Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
# Conflicts: # src/components/Dicom/DicomViewer.vueuat_us
commit
5a1b64a023
|
|
@ -79,16 +79,18 @@
|
||||||
import * as cornerstone from 'cornerstone-core'
|
import * as cornerstone from 'cornerstone-core'
|
||||||
import * as cornerstoneMath from 'cornerstone-math'
|
import * as cornerstoneMath from 'cornerstone-math'
|
||||||
import * as cornerstoneTools from 'cornerstone-tools'
|
import * as cornerstoneTools from 'cornerstone-tools'
|
||||||
|
import metaDataProvider from '@/utils/metaDataProvider'
|
||||||
|
|
||||||
const scroll = cornerstoneTools.import('util/scrollToIndex')
|
const scroll = cornerstoneTools.import('util/scrollToIndex')
|
||||||
import Hammer from 'hammerjs'
|
import Hammer from 'hammerjs'
|
||||||
import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/getOrientationString'
|
import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/getOrientationString'
|
||||||
import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString'
|
import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString'
|
||||||
|
|
||||||
import calculateSUV from '@/views/trials/trials-panel/reading/dicoms/tools/calculateSUV'
|
const calculateSUV = cornerstoneTools.import('util/calculateSUV')
|
||||||
// import requestPoolManager from '@/utils/request-pool'
|
// import requestPoolManager from '@/utils/request-pool'
|
||||||
import ScaleOverlayTool from '@/views/trials/trials-panel/reading/dicoms/tools/ScaleOverlay/ScaleOverlayTool'
|
import ScaleOverlayTool from '@/views/trials/trials-panel/reading/dicoms/tools/ScaleOverlay/ScaleOverlayTool'
|
||||||
import Note_RectangleRoiTool from '@/views/trials/trials-panel/reading/dicoms/tools/RectangleRoi/Note_RectangleRoiTool'
|
import Note_RectangleRoiTool from '@/views/trials/trials-panel/reading/dicoms/tools/RectangleRoi/Note_RectangleRoiTool'
|
||||||
|
let isMetaDataProviderAdded = false
|
||||||
cornerstoneTools.external.cornerstone = cornerstone
|
cornerstoneTools.external.cornerstone = cornerstone
|
||||||
cornerstoneTools.external.Hammer = Hammer
|
cornerstoneTools.external.Hammer = Hammer
|
||||||
cornerstoneTools.external.cornerstoneMath = cornerstoneMath
|
cornerstoneTools.external.cornerstoneMath = cornerstoneMath
|
||||||
|
|
@ -219,6 +221,11 @@ export default {
|
||||||
// this.stack.instanceId = instanceId
|
// this.stack.instanceId = instanceId
|
||||||
this.toolState.clipPlaying = false
|
this.toolState.clipPlaying = false
|
||||||
const element = this.$refs.canvas
|
const element = this.$refs.canvas
|
||||||
|
if (!isMetaDataProviderAdded) {
|
||||||
|
// 注册自定义 metaDataProvider:统一 SUV 口径(优先接口,缺失回退 DICOM)
|
||||||
|
cornerstone.metaData.addProvider(metaDataProvider, 100000)
|
||||||
|
isMetaDataProviderAdded = true
|
||||||
|
}
|
||||||
cornerstone.enable(element)
|
cornerstone.enable(element)
|
||||||
cornerstoneTools.stopClip(this.canvas)
|
cornerstoneTools.stopClip(this.canvas)
|
||||||
this.toolState.clipPlaying = false
|
this.toolState.clipPlaying = false
|
||||||
|
|
@ -388,7 +395,7 @@ export default {
|
||||||
this.dicomInfo.age = data.string('x00101010')
|
this.dicomInfo.age = data.string('x00101010')
|
||||||
this.dicomInfo.sex = data.string('x00100040')
|
this.dicomInfo.sex = data.string('x00100040')
|
||||||
this.dicomInfo.acc = data.string('x00080050') // 登记号
|
this.dicomInfo.acc = data.string('x00080050') // 登记号
|
||||||
this.dicomInfo.modality = data.string('x00080060')
|
this.dicomInfo.modality = (data.string('x00080060') || '').trim()
|
||||||
this.dicomInfo.time = this.formatDicomDateTime(
|
this.dicomInfo.time = this.formatDicomDateTime(
|
||||||
data.string('x00080020'),
|
data.string('x00080020'),
|
||||||
data.string('x00080030')
|
data.string('x00080030')
|
||||||
|
|
|
||||||
|
|
@ -296,14 +296,14 @@
|
||||||
|
|
||||||
<!-- 患者信息 -->
|
<!-- 患者信息 -->
|
||||||
<div class="measureTool-wrapper patient-form"
|
<div class="measureTool-wrapper patient-form"
|
||||||
v-if="isHaveStudyClinicalData && type === 'Study' && modality && ['PT、CT', 'CT、PT', 'PET-CT'].includes(modality)">
|
v-if="type === 'Study' && modality && ['PT、CT', 'CT、PT', 'PET-CT'].includes(modality)">
|
||||||
<div class="sideTool-title">{{ $t('trials:tab:patientData') }}</div>
|
<div class="sideTool-title">{{ $t('trials:tab:patientData') }}</div>
|
||||||
<div class="sideTool-wrapper">
|
<div class="sideTool-wrapper">
|
||||||
<el-form ref="patientForm" :model="formData" :rules="rules" label-width="150" v-loading="formLoading">
|
<el-form ref="patientForm" size="mini" :model="formData" :rules="rules" label-width="150" v-loading="formLoading">
|
||||||
<!-- 性别 -->
|
<!-- 性别 -->
|
||||||
<el-form-item :label="$t('trials:ptData:label:patientSex')" prop="PatientSex">
|
<el-form-item :label="$t('trials:ptData:label:patientSex')" prop="PatientSex">
|
||||||
<el-select v-model="formData.PatientSex" :placeholder="$t('common:ruleMessage:select')"
|
<el-select v-model="formData.PatientSex" :placeholder="$t('common:ruleMessage:select')"
|
||||||
style="width: 100%" size="small" :disabled="!isEdit">
|
style="width: 100%" :disabled="!isEdit">
|
||||||
<el-option :label="$t('trials:patientSex:male')" value="M"></el-option>
|
<el-option :label="$t('trials:patientSex:male')" value="M"></el-option>
|
||||||
<el-option :label="$t('trials:patientSex:female')" value="F"></el-option>
|
<el-option :label="$t('trials:patientSex:female')" value="F"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
@ -311,32 +311,32 @@
|
||||||
<!-- 体重(kg) 例如 70.5-->
|
<!-- 体重(kg) 例如 70.5-->
|
||||||
<el-form-item :label="$t('trials:ptData:label:patientWeight')" prop="PatientWeight">
|
<el-form-item :label="$t('trials:ptData:label:patientWeight')" prop="PatientWeight">
|
||||||
<el-input v-model.number="formData.PatientWeight" :placeholder="$t('trials:patientWeight:eg')"
|
<el-input v-model.number="formData.PatientWeight" :placeholder="$t('trials:patientWeight:eg')"
|
||||||
style="width: 100%" size="small" :disabled="!isEdit"></el-input>
|
style="width: 100%" :disabled="!isEdit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 总剂量(Bq) 例如 740000000-->
|
<!-- 总剂量(Bq) 例如 740000000-->
|
||||||
<el-form-item :label="$t('trials:ptData:label:totalDose')" prop="RadionuclideTotalDose">
|
<el-form-item :label="$t('trials:ptData:label:totalDose')" prop="RadionuclideTotalDose">
|
||||||
<el-input v-model.number="formData.RadionuclideTotalDose" :placeholder="$t('trials:totalDose:eg')"
|
<el-input v-model.number="formData.RadionuclideTotalDose" :placeholder="$t('trials:totalDose:eg')"
|
||||||
style="width: 100%" size="small" :disabled="!isEdit"></el-input>
|
style="width: 100%" :disabled="!isEdit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 半衰期(s) 例如 21600-->
|
<!-- 半衰期(s) 例如 21600-->
|
||||||
<el-form-item :label="$t('trials:ptData:label:halfLife')" prop="RadionuclideHalfLife">
|
<el-form-item :label="$t('trials:ptData:label:halfLife')" prop="RadionuclideHalfLife">
|
||||||
<el-input v-model.number="formData.RadionuclideHalfLife" :placeholder="$t('trials:halfLife:eg')"
|
<el-input v-model.number="formData.RadionuclideHalfLife" :placeholder="$t('trials:halfLife:eg')"
|
||||||
style="width: 100%" size="small" :disabled="!isEdit"></el-input>
|
style="width: 100%" :disabled="!isEdit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 注射时间(s) Unix 秒 或 相对秒-->
|
<!-- 注射时间(s) Unix 秒 或 相对秒-->
|
||||||
<el-form-item :label="$t('trials:ptData:label:injectTime')" prop="RadiopharmaceuticalStartTime">
|
<el-form-item :label="$t('trials:ptData:label:injectTime')" prop="RadiopharmaceuticalStartTime">
|
||||||
<el-input v-model.number="formData.RadiopharmaceuticalStartTime" :placeholder="$t('trials:injectTime:eg')"
|
<el-input v-model.number="formData.RadiopharmaceuticalStartTime" :placeholder="$t('trials:injectTime:eg')"
|
||||||
style="width: 100%" @input="computeTimeRelation" size="small" :disabled="!isEdit"></el-input>
|
style="width: 100%" @input="computeTimeRelation" :disabled="!isEdit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 成像时间(s) Unix 秒 或 相对秒-->
|
<!-- 成像时间(s) Unix 秒 或 相对秒-->
|
||||||
<el-form-item :label="$t('trials:ptData:label:acquisitionTime')" prop="AcquisitionTime">
|
<el-form-item :label="$t('trials:ptData:label:acquisitionTime')" prop="AcquisitionTime">
|
||||||
<el-input v-model.number="formData.AcquisitionTime" :placeholder="$t('trials:injectTime:eg')"
|
<el-input v-model.number="formData.AcquisitionTime" :placeholder="$t('trials:injectTime:eg')"
|
||||||
style="width: 100%" @input="computeTimeRelation" size="small" :disabled="!isEdit"></el-input>
|
style="width: 100%" @input="computeTimeRelation" :disabled="!isEdit"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 时间一致性检查 -->
|
<!-- 时间一致性检查 -->
|
||||||
<el-form-item :label="$t('trials:ptData:label:timeCheck')">
|
<!-- <el-form-item :label="$t('trials:ptData:label:timeCheck')">
|
||||||
<el-input v-model="formData.TimeCheck" disabled style="width: 100%" size="small"></el-input>
|
<el-input v-model="formData.TimeCheck" disabled style="width: 100%"></el-input>
|
||||||
</el-form-item>
|
</el-form-item> -->
|
||||||
|
|
||||||
<!-- 提交 -->
|
<!-- 提交 -->
|
||||||
<el-form-item style="margin-top: 20px;text-align: right;" v-if="isEdit">
|
<el-form-item style="margin-top: 20px;text-align: right;" v-if="isEdit">
|
||||||
|
|
@ -374,6 +374,7 @@ import {
|
||||||
getPatientInfo,
|
getPatientInfo,
|
||||||
editPatientInfo
|
editPatientInfo
|
||||||
} from '@/api/trials'
|
} from '@/api/trials'
|
||||||
|
import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache'
|
||||||
export default {
|
export default {
|
||||||
name: 'DicomsViewer',
|
name: 'DicomsViewer',
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -394,6 +395,20 @@ export default {
|
||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
modality: {
|
||||||
|
immediate: true,
|
||||||
|
handler(v) {
|
||||||
|
if (v) {
|
||||||
|
if (this.type === 'Study' && ['PT、CT', 'CT、PT', 'PET-CT'].includes(v)) {
|
||||||
|
this.$nextTick(()=>{
|
||||||
|
this.getPatientInfo()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hasAnonymous: false,
|
hasAnonymous: false,
|
||||||
|
|
@ -460,7 +475,6 @@ export default {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
formLoading: false,
|
formLoading: false,
|
||||||
isHaveStudyClinicalData: false,
|
|
||||||
type: '',
|
type: '',
|
||||||
isEdit: 0
|
isEdit: 0
|
||||||
}
|
}
|
||||||
|
|
@ -477,12 +491,11 @@ export default {
|
||||||
this.wwwcList[0] = '-1'
|
this.wwwcList[0] = '-1'
|
||||||
this.colormapsList = cornerstone.colors.getColormapsList()
|
this.colormapsList = cornerstone.colors.getColormapsList()
|
||||||
this.currentDicomCanvas = this.$refs['dicomCanvas0']
|
this.currentDicomCanvas = this.$refs['dicomCanvas0']
|
||||||
this.isHaveStudyClinicalData = this.$route.query.isHaveStudyClinicalData === 'true'
|
|
||||||
this.type = this.$route.query.type
|
this.type = this.$route.query.type
|
||||||
this.isEdit = parseInt(this.$route.query.showDelete)
|
this.isEdit = parseInt(this.$route.query.showDelete)
|
||||||
if (this.isHaveStudyClinicalData && this.type === 'Study' && ['PT、CT', 'CT、PT', 'PET-CT'].includes(this.modality)) {
|
},
|
||||||
this.getPatientInfo()
|
beforeDestroy() {
|
||||||
}
|
clearPTClinicalDataCache()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -712,7 +725,17 @@ export default {
|
||||||
this.currentDicomCanvas.toolState.clipPlaying = false
|
this.currentDicomCanvas.toolState.clipPlaying = false
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.series = Object.assign({}, dicomSeries)
|
this.series = Object.assign({}, dicomSeries)
|
||||||
|
this.seriesList = [this.series]
|
||||||
this.currentDicomCanvas.loadImageStack(this.series)
|
this.currentDicomCanvas.loadImageStack(this.series)
|
||||||
|
if (
|
||||||
|
this.formData.PatientWeight != null ||
|
||||||
|
this.formData.RadionuclideTotalDose != null ||
|
||||||
|
this.formData.RadionuclideHalfLife != null ||
|
||||||
|
this.formData.RadiopharmaceuticalStartTime != null ||
|
||||||
|
this.formData.AcquisitionTime != null
|
||||||
|
) {
|
||||||
|
this.cachePtClinicalDataToInstances()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
loadOtherImageStack(seriesList) {
|
loadOtherImageStack(seriesList) {
|
||||||
|
|
@ -726,6 +749,15 @@ export default {
|
||||||
this.$refs[`dicomCanvas${canvasIndex}`].loadImageStack(series)
|
this.$refs[`dicomCanvas${canvasIndex}`].loadImageStack(series)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (
|
||||||
|
this.formData.PatientWeight != null ||
|
||||||
|
this.formData.RadionuclideTotalDose != null ||
|
||||||
|
this.formData.RadionuclideHalfLife != null ||
|
||||||
|
this.formData.RadiopharmaceuticalStartTime != null ||
|
||||||
|
this.formData.AcquisitionTime != null
|
||||||
|
) {
|
||||||
|
this.cachePtClinicalDataToInstances()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
activateDicomCanvas(index) {
|
activateDicomCanvas(index) {
|
||||||
|
|
@ -988,7 +1020,7 @@ export default {
|
||||||
try {
|
try {
|
||||||
this.formLoading = true
|
this.formLoading = true
|
||||||
let studyId = this.$route.query.studyId
|
let studyId = this.$route.query.studyId
|
||||||
let res = await getPatientInfo({ studyId: studyId })
|
let res = await getPatientInfo({studyId: studyId})
|
||||||
this.formData = {
|
this.formData = {
|
||||||
Id: res.Result.Id || '',
|
Id: res.Result.Id || '',
|
||||||
PatientSex: res.Result.PatientSex || '',
|
PatientSex: res.Result.PatientSex || '',
|
||||||
|
|
@ -1000,12 +1032,88 @@ export default {
|
||||||
TimeCheck: ''
|
TimeCheck: ''
|
||||||
}
|
}
|
||||||
this.computeTimeRelation()
|
this.computeTimeRelation()
|
||||||
|
// 缓存 PT 临床数据:用于 2D SUV 计算时优先使用接口/人工录入值
|
||||||
|
this.cachePtClinicalDataToInstances()
|
||||||
this.formLoading = false
|
this.formLoading = false
|
||||||
} catch (e) {
|
} catch(e) {
|
||||||
this.formLoading = false
|
this.formLoading = false
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
cachePtClinicalDataToInstances() {
|
||||||
|
const clinicalData = {
|
||||||
|
PatientSex: this.formData.PatientSex,
|
||||||
|
PatientWeight: this.formData.PatientWeight,
|
||||||
|
RadionuclideTotalDose: this.formData.RadionuclideTotalDose,
|
||||||
|
RadionuclideHalfLife: this.formData.RadionuclideHalfLife,
|
||||||
|
RadiopharmaceuticalStartTime: this.formData.RadiopharmaceuticalStartTime,
|
||||||
|
AcquisitionTime: this.formData.AcquisitionTime
|
||||||
|
}
|
||||||
|
const seriesList = Array.isArray(this.seriesList) ? this.seriesList : []
|
||||||
|
seriesList.forEach(series => {
|
||||||
|
const instanceInfoList = series?.instanceInfoList
|
||||||
|
if (Array.isArray(instanceInfoList) && instanceInfoList.length > 0) {
|
||||||
|
instanceInfoList.forEach(instance => {
|
||||||
|
if (instance && instance.Id != null) {
|
||||||
|
setPTClinicalDataForInstance(instance.Id, clinicalData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const imageIds = series?.imageIds
|
||||||
|
if (Array.isArray(imageIds) && imageIds.length > 0) {
|
||||||
|
imageIds.forEach(imageId => {
|
||||||
|
const instanceId = this.getInstanceIdFromImageId(imageId)
|
||||||
|
if (instanceId) {
|
||||||
|
setPTClinicalDataForInstance(instanceId, clinicalData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getInstanceIdFromImageId(imageId) {
|
||||||
|
try {
|
||||||
|
const qIndex = String(imageId).indexOf('?')
|
||||||
|
if (qIndex === -1) return null
|
||||||
|
const params = new URLSearchParams(String(imageId).slice(qIndex + 1))
|
||||||
|
const instanceId = params.get('instanceId')
|
||||||
|
return instanceId ? String(instanceId).trim() : null
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshDicomAfterClinicalDataChanged() {
|
||||||
|
// 患者信息保存后,强制刷新画布与标注统计,确保 SUV 等数据显示使用最新的 PT 临床数据口径
|
||||||
|
const toolTypes = [
|
||||||
|
'Probe',
|
||||||
|
'RectangleRoi',
|
||||||
|
'EllipticalRoi',
|
||||||
|
'FreehandRoi',
|
||||||
|
'Bidirectional',
|
||||||
|
'Length',
|
||||||
|
'ArrowAnnotate',
|
||||||
|
'Angle',
|
||||||
|
'CobbAngle'
|
||||||
|
]
|
||||||
|
const elements = document.querySelectorAll('.dicom-item')
|
||||||
|
Array.from(elements).forEach((wrapper) => {
|
||||||
|
const index = wrapper.getAttribute('data-index')
|
||||||
|
const canvasComp = index !== null ? this.$refs[`dicomCanvas${index}`] : null
|
||||||
|
const element = canvasComp?.$refs?.canvas
|
||||||
|
if (!element) return
|
||||||
|
toolTypes.forEach((toolType) => {
|
||||||
|
const toolState = cornerstoneTools.getToolState(element, toolType)
|
||||||
|
if (toolState && Array.isArray(toolState.data)) {
|
||||||
|
toolState.data.forEach((d) => {
|
||||||
|
if (d && typeof d === 'object') {
|
||||||
|
d.invalidated = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cornerstone.updateImage(element, true)
|
||||||
|
})
|
||||||
|
},
|
||||||
async submitForm() {
|
async submitForm() {
|
||||||
try {
|
try {
|
||||||
let valid = await this.$refs.patientForm.validate()
|
let valid = await this.$refs.patientForm.validate()
|
||||||
|
|
@ -1015,6 +1123,8 @@ export default {
|
||||||
this.formLoading = false
|
this.formLoading = false
|
||||||
if (res.IsSuccess) {
|
if (res.IsSuccess) {
|
||||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||||
|
this.cachePtClinicalDataToInstances()
|
||||||
|
this.refreshDicomAfterClinicalDataChanged()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.formLoading = false
|
this.formLoading = false
|
||||||
|
|
@ -1104,6 +1214,26 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Anonymous .btn {
|
||||||
|
width: 15%;
|
||||||
|
text-align: center;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
background-color: rgba(255, 255, 255, .3);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, .7);
|
||||||
|
}
|
||||||
|
.Anonymous .btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Anonymous .activeBtn {
|
||||||
|
background-color: rgba(255, 255, 255, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.btnBox {
|
.btnBox {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
|
|
@ -1372,11 +1502,20 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.patient-form .el-form-item {
|
.patient-form .el-form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.patient-form .el-form-item__label {
|
.patient-form .el-form-item__label {
|
||||||
color: #d0d0d0;
|
color: #d0d0d0;
|
||||||
|
flex: 0 0 150px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-form .el-form-item__content {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.patient-form .el-input.is-disabled .el-input__inner {
|
.patient-form .el-input.is-disabled .el-input__inner {
|
||||||
|
|
|
||||||
|
|
@ -932,6 +932,12 @@ const actions = {
|
||||||
data.IsDicom = study.IsDicom
|
data.IsDicom = study.IsDicom
|
||||||
data.PreviewImageCount = 0
|
data.PreviewImageCount = 0
|
||||||
data.IsCriticalSequence = study.IsCriticalSequence
|
data.IsCriticalSequence = study.IsCriticalSequence
|
||||||
|
data.PatientSex = study.PatientSex
|
||||||
|
data.PatientWeight = study.PatientWeight
|
||||||
|
data.RadionuclideHalfLife = study.RadionuclideHalfLife
|
||||||
|
data.RadionuclideTotalDose = study.RadionuclideTotalDose
|
||||||
|
data.RadiopharmaceuticalStartTime = study.RadiopharmaceuticalStartTime
|
||||||
|
data.AcquisitionTime = study.AcquisitionTime
|
||||||
var seriesList = []
|
var seriesList = []
|
||||||
study.SeriesList.forEach((series, seriesIndex) => {
|
study.SeriesList.forEach((series, seriesIndex) => {
|
||||||
const imageIds = []
|
const imageIds = []
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,123 @@
|
||||||
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
|
import * as cornerstoneWADOImageLoader from "cornerstone-wado-image-loader";
|
||||||
|
import store from "@/store";
|
||||||
|
import { getPTClinicalDataForInstance } from "@/utils/ptClinicalDataCache";
|
||||||
import {
|
import {
|
||||||
getImageTypeSubItemFromDataset,
|
getImageTypeSubItemFromDataset,
|
||||||
extractOrientationFromDataset,
|
extractOrientationFromDataset,
|
||||||
extractPositionFromDataset,
|
extractPositionFromDataset,
|
||||||
extractSpacingFromDataset,
|
extractSpacingFromDataset,
|
||||||
extractSliceThicknessFromDataset,
|
extractSliceThicknessFromDataset,
|
||||||
} from './extractPositioningFromDataset';
|
} from "./extractPositioningFromDataset";
|
||||||
|
|
||||||
|
function toNumber(val) {
|
||||||
|
if (val === undefined || val === null || val === "") return null;
|
||||||
|
const n = typeof val === "number" ? val : parseFloat(val);
|
||||||
|
return Number.isFinite(n) ? n : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDicomTime(value) {
|
||||||
|
if (value === undefined || value === null || value === "") return null;
|
||||||
|
const num = toNumber(value);
|
||||||
|
if (num != null) {
|
||||||
|
return String(Math.floor(num)).padStart(6, "0");
|
||||||
|
}
|
||||||
|
if (typeof value !== "string") return null;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
const plain = trimmed.replace(/:/g, "");
|
||||||
|
const parts = plain.split(".");
|
||||||
|
const main = parts[0];
|
||||||
|
const fractional = parts[1];
|
||||||
|
if (!/^\d+$/.test(main)) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
const normalizedMain = main.padStart(6, "0");
|
||||||
|
return fractional ? `${normalizedMain}.${fractional}` : normalizedMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPTClinicalOverrideFromImageId(imageId) {
|
||||||
|
try {
|
||||||
|
const qIndex = imageId.indexOf("?");
|
||||||
|
if (qIndex === -1) return null;
|
||||||
|
const params = new URLSearchParams(imageId.slice(qIndex + 1));
|
||||||
|
const visitTaskId = params.get("visitTaskId");
|
||||||
|
const idx = params.get("idx");
|
||||||
|
|
||||||
|
// 场景1:trials/dicoms,imageId 带 visitTaskId + idx,优先使用列表中的最新 study 值
|
||||||
|
if (visitTaskId && idx) {
|
||||||
|
const parts = idx.split("|");
|
||||||
|
const studyIndex = toNumber(parts[0]);
|
||||||
|
if (Number.isInteger(studyIndex) && studyIndex >= 0) {
|
||||||
|
const visitTaskList = store.state.reading.visitTaskList;
|
||||||
|
if (Array.isArray(visitTaskList)) {
|
||||||
|
const visitTaskInfo = visitTaskList.find(
|
||||||
|
(v) => v && String(v.VisitTaskId) === visitTaskId
|
||||||
|
);
|
||||||
|
const study = visitTaskInfo?.StudyList?.[studyIndex];
|
||||||
|
if (
|
||||||
|
study &&
|
||||||
|
!(
|
||||||
|
study.PatientWeight == null &&
|
||||||
|
study.RadionuclideTotalDose == null &&
|
||||||
|
study.RadionuclideHalfLife == null &&
|
||||||
|
study.RadiopharmaceuticalStartTime == null &&
|
||||||
|
study.AcquisitionTime == null &&
|
||||||
|
study.PatientSex == null
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return study;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景2:src/components/Dicom,imageId 仅带 instanceId,回退到实例缓存
|
||||||
|
const instanceId = params.get("instanceId");
|
||||||
|
if (instanceId) {
|
||||||
|
const cached = getPTClinicalDataForInstance(instanceId);
|
||||||
|
if (cached) return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
function parseImageId(imageId) {
|
function parseImageId(imageId) {
|
||||||
// build a url by parsing out the url scheme and frame index from the imageId
|
// 兼容 frame 参数不在最后的情况:只移除 frame 参数本身,保留 instanceId/visitTaskId/idx 等其余 query
|
||||||
const firstColonIndex = imageId.indexOf(':');
|
const firstColonIndex = imageId.indexOf(":");
|
||||||
|
const scheme = imageId.substr(0, firstColonIndex);
|
||||||
let url = imageId.substring(firstColonIndex + 1);
|
const urlPart = imageId.substring(firstColonIndex + 1);
|
||||||
const frameIndex = url.indexOf('frame=');
|
let url = urlPart;
|
||||||
|
|
||||||
let frame;
|
let frame;
|
||||||
|
const qIndex = urlPart.indexOf("?");
|
||||||
|
if (qIndex !== -1) {
|
||||||
|
const base = urlPart.slice(0, qIndex);
|
||||||
|
const query = urlPart.slice(qIndex + 1);
|
||||||
|
const parts = query.split("&").filter(Boolean);
|
||||||
|
const preservedParts = [];
|
||||||
|
|
||||||
if (frameIndex !== -1) {
|
parts.forEach((part) => {
|
||||||
const frameStr = url.substr(frameIndex + 6);
|
const eqIndex = part.indexOf("=");
|
||||||
|
const key = eqIndex === -1 ? part : part.slice(0, eqIndex);
|
||||||
|
const value = eqIndex === -1 ? "" : part.slice(eqIndex + 1);
|
||||||
|
|
||||||
frame = parseInt(frameStr, 10);
|
if (key === "frame") {
|
||||||
url = url.substr(0, frameIndex - 1);
|
const n = value !== "" ? parseInt(value, 10) : NaN;
|
||||||
|
if (Number.isFinite(n)) {
|
||||||
|
frame = n;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留原始 query 片段,避免 URLSearchParams 把 | 编码成 %7C 导致 dataSetCache key 对不上
|
||||||
|
preservedParts.push(part);
|
||||||
|
});
|
||||||
|
|
||||||
|
url = preservedParts.length ? `${base}?${preservedParts.join("&")}` : base;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { scheme, url, frame };
|
||||||
scheme: imageId.substr(0, firstColonIndex),
|
|
||||||
url,
|
|
||||||
frame,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
function getNumberValues(dataSet, tag, minimumLength) {
|
function getNumberValues(dataSet, tag, minimumLength) {
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
@ -35,7 +126,7 @@ function getNumberValues(dataSet, tag, minimumLength) {
|
||||||
if (!valueAsString) {
|
if (!valueAsString) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const split = valueAsString.split('\\');
|
const split = valueAsString.split("\\");
|
||||||
|
|
||||||
if (minimumLength && split.length < minimumLength) {
|
if (minimumLength && split.length < minimumLength) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -74,28 +165,31 @@ function getLutData(lutDataSet, tag, lutDescriptor) {
|
||||||
return lut;
|
return lut;
|
||||||
}
|
}
|
||||||
function populateSmallestLargestPixelValues(dataSet, imagePixelModule) {
|
function populateSmallestLargestPixelValues(dataSet, imagePixelModule) {
|
||||||
const pixelRepresentation = dataSet.uint16('x00280103');
|
const pixelRepresentation = dataSet.uint16("x00280103");
|
||||||
if (pixelRepresentation === 0) {
|
if (pixelRepresentation === 0) {
|
||||||
imagePixelModule.smallestPixelValue = dataSet.uint16('x00280106');
|
imagePixelModule.smallestPixelValue = dataSet.uint16("x00280106");
|
||||||
imagePixelModule.largestPixelValue = dataSet.uint16('x00280107');
|
imagePixelModule.largestPixelValue = dataSet.uint16("x00280107");
|
||||||
} else {
|
} else {
|
||||||
imagePixelModule.smallestPixelValue = dataSet.int16('x00280106');
|
imagePixelModule.smallestPixelValue = dataSet.int16("x00280106");
|
||||||
imagePixelModule.largestPixelValue = dataSet.int16('x00280107');
|
imagePixelModule.largestPixelValue = dataSet.int16("x00280107");
|
||||||
}
|
}
|
||||||
imagePixelModule.largestPixelValue = imagePixelModule.largestPixelValue === 0 ? undefined : imagePixelModule.largestPixelValue;
|
imagePixelModule.largestPixelValue =
|
||||||
|
imagePixelModule.largestPixelValue === 0
|
||||||
|
? undefined
|
||||||
|
: imagePixelModule.largestPixelValue;
|
||||||
}
|
}
|
||||||
function populatePaletteColorLut(dataSet, imagePixelModule) {
|
function populatePaletteColorLut(dataSet, imagePixelModule) {
|
||||||
imagePixelModule.redPaletteColorLookupTableDescriptor = getLutDescriptor(
|
imagePixelModule.redPaletteColorLookupTableDescriptor = getLutDescriptor(
|
||||||
dataSet,
|
dataSet,
|
||||||
'x00281101'
|
"x00281101"
|
||||||
);
|
);
|
||||||
imagePixelModule.greenPaletteColorLookupTableDescriptor = getLutDescriptor(
|
imagePixelModule.greenPaletteColorLookupTableDescriptor = getLutDescriptor(
|
||||||
dataSet,
|
dataSet,
|
||||||
'x00281102'
|
"x00281102"
|
||||||
);
|
);
|
||||||
imagePixelModule.bluePaletteColorLookupTableDescriptor = getLutDescriptor(
|
imagePixelModule.bluePaletteColorLookupTableDescriptor = getLutDescriptor(
|
||||||
dataSet,
|
dataSet,
|
||||||
'x00281103'
|
"x00281103"
|
||||||
);
|
);
|
||||||
|
|
||||||
// The first Palette Color Lookup Table Descriptor value is the number of entries in the lookup table.
|
// The first Palette Color Lookup Table Descriptor value is the number of entries in the lookup table.
|
||||||
|
|
@ -134,56 +228,165 @@ function populatePaletteColorLut(dataSet, imagePixelModule) {
|
||||||
|
|
||||||
imagePixelModule.redPaletteColorLookupTableData = getLutData(
|
imagePixelModule.redPaletteColorLookupTableData = getLutData(
|
||||||
dataSet,
|
dataSet,
|
||||||
'x00281201',
|
"x00281201",
|
||||||
imagePixelModule.redPaletteColorLookupTableDescriptor
|
imagePixelModule.redPaletteColorLookupTableDescriptor
|
||||||
);
|
);
|
||||||
imagePixelModule.greenPaletteColorLookupTableData = getLutData(
|
imagePixelModule.greenPaletteColorLookupTableData = getLutData(
|
||||||
dataSet,
|
dataSet,
|
||||||
'x00281202',
|
"x00281202",
|
||||||
imagePixelModule.greenPaletteColorLookupTableDescriptor
|
imagePixelModule.greenPaletteColorLookupTableDescriptor
|
||||||
);
|
);
|
||||||
imagePixelModule.bluePaletteColorLookupTableData = getLutData(
|
imagePixelModule.bluePaletteColorLookupTableData = getLutData(
|
||||||
dataSet,
|
dataSet,
|
||||||
'x00281203',
|
"x00281203",
|
||||||
imagePixelModule.bluePaletteColorLookupTableDescriptor
|
imagePixelModule.bluePaletteColorLookupTableDescriptor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpacingBetweenSlices(dataSet) {
|
function getSpacingBetweenSlices(dataSet) {
|
||||||
if (dataSet?.elements?.x00180088) {
|
if (dataSet?.elements?.x00180088) {
|
||||||
return dataSet.floatString('x00180088');
|
return dataSet.floatString("x00180088");
|
||||||
}
|
}
|
||||||
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
|
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
|
||||||
if (
|
if (
|
||||||
pixelMeasuresSequence?.items?.length &&
|
pixelMeasuresSequence?.items?.length &&
|
||||||
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
|
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
|
||||||
) {
|
) {
|
||||||
return pixelMeasuresSequence.items[0].dataSet.floatString('x00180088');
|
return pixelMeasuresSequence.items[0].dataSet.floatString("x00180088");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function metaDataProvider(type, imageId) {
|
function metaDataProvider(type, imageId) {
|
||||||
const parsedImageId = parseImageId(imageId);
|
const parsedImageId = parseImageId(imageId);
|
||||||
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
|
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(
|
||||||
|
parsedImageId.url
|
||||||
|
);
|
||||||
|
|
||||||
if (!dataSet) {
|
if (!dataSet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === 'imagePlaneModule') {
|
const ptOverride = getPTClinicalOverrideFromImageId(imageId);
|
||||||
|
if (type === "generalSeriesModule") {
|
||||||
|
const { dicomParser } = cornerstoneWADOImageLoader.external || {};
|
||||||
|
const overrideTime = normalizeDicomTime(ptOverride?.AcquisitionTime);
|
||||||
|
const seriesTimeRaw =
|
||||||
|
overrideTime ||
|
||||||
|
dataSet.string("x00080031") ||
|
||||||
|
dataSet.string("x00080032") ||
|
||||||
|
"";
|
||||||
|
const acquisitionTimeRaw =
|
||||||
|
overrideTime ||
|
||||||
|
dataSet.string("x00080032") ||
|
||||||
|
dataSet.string("x00080031") ||
|
||||||
|
"";
|
||||||
|
return {
|
||||||
|
modality: dataSet.string("x00080060"),
|
||||||
|
seriesInstanceUID: dataSet.string("x0020000e"),
|
||||||
|
seriesNumber: dataSet.intString("x00200011"),
|
||||||
|
studyInstanceUID: dataSet.string("x0020000d"),
|
||||||
|
seriesDate: dicomParser.parseDA(dataSet.string("x00080021")),
|
||||||
|
// 2D SUV 计算读取的是 seriesTime,这里同步使用用户录入采集时间
|
||||||
|
// seriesTime: dicomParser.parseTM(String(seriesTimeRaw)),
|
||||||
|
seriesTime: dicomParser.parseTM(dataSet.string('x00080031') || ''),
|
||||||
|
acquisitionDate: dicomParser.parseDA(dataSet.string("x00080022") || ""),
|
||||||
|
acquisitionTime: dicomParser.parseTM(String(acquisitionTimeRaw)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (type === "patientStudyModule") {
|
||||||
|
const weightOverride = ptOverride?.PatientWeight;
|
||||||
|
const weight =
|
||||||
|
weightOverride
|
||||||
|
? toNumber(weightOverride)
|
||||||
|
: dataSet.floatString("x00101030");
|
||||||
|
return {
|
||||||
|
patientAge: dataSet.intString("x00101010"),
|
||||||
|
patientSize: dataSet.floatString("x00101020"),
|
||||||
|
patientWeight: weight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "petIsotopeModule") {
|
||||||
|
const { dicomParser } = cornerstoneWADOImageLoader.external || {};
|
||||||
|
const radiopharmaceuticalInfo = dataSet.elements.x00540016;
|
||||||
|
|
||||||
|
if (radiopharmaceuticalInfo === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstRadiopharmaceuticalInfoDataSet =
|
||||||
|
radiopharmaceuticalInfo.items[0].dataSet;
|
||||||
|
|
||||||
|
const startTimeRaw =
|
||||||
|
firstRadiopharmaceuticalInfoDataSet.string("x00181072") || "";
|
||||||
|
const totalDoseRaw =
|
||||||
|
firstRadiopharmaceuticalInfoDataSet.floatString("x00181074");
|
||||||
|
const halfLifeRaw =
|
||||||
|
firstRadiopharmaceuticalInfoDataSet.floatString("x00181075");
|
||||||
|
let startTimeValue = normalizeDicomTime(
|
||||||
|
ptOverride?.RadiopharmaceuticalStartTime
|
||||||
|
);
|
||||||
|
if (!startTimeValue && startTimeRaw) {
|
||||||
|
startTimeValue = normalizeDicomTime(startTimeRaw) || startTimeRaw;
|
||||||
|
}
|
||||||
|
const radiopharmaceuticalStartTime = dicomParser.parseTM(
|
||||||
|
String(startTimeValue)
|
||||||
|
);
|
||||||
|
const overrideTotalDose =
|
||||||
|
ptOverride?.RadionuclideTotalDose != null &&
|
||||||
|
ptOverride.RadionuclideTotalDose !== ""
|
||||||
|
? toNumber(ptOverride.RadionuclideTotalDose)
|
||||||
|
: null;
|
||||||
|
const overrideHalfLife =
|
||||||
|
ptOverride?.RadionuclideHalfLife != null &&
|
||||||
|
ptOverride.RadionuclideHalfLife !== ""
|
||||||
|
? toNumber(ptOverride.RadionuclideHalfLife)
|
||||||
|
: null;
|
||||||
|
const radionuclideTotalDose =
|
||||||
|
overrideTotalDose != null ? overrideTotalDose : toNumber(totalDoseRaw);
|
||||||
|
const radionuclideHalfLife =
|
||||||
|
overrideHalfLife != null ? overrideHalfLife : toNumber(halfLifeRaw);
|
||||||
|
return {
|
||||||
|
radiopharmaceuticalInfo: {
|
||||||
|
radiopharmaceuticalStartTime,
|
||||||
|
radionuclideTotalDose,
|
||||||
|
radionuclideHalfLife,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (type === "imagePlaneModule") {
|
||||||
// const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
// const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
||||||
// const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
// const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||||
// const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
// const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
||||||
const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2);
|
const imagePixelSpacing = getNumberValues(dataSet, "x00181164", 2);
|
||||||
const frameIndex = parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
|
const frameIndex =
|
||||||
const imageOrientationPatient = extractOrientationFromDataset(dataSet, frameIndex);
|
parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
|
||||||
|
const imageOrientationPatient = extractOrientationFromDataset(
|
||||||
|
dataSet,
|
||||||
|
frameIndex
|
||||||
|
);
|
||||||
let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
|
let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
|
||||||
const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
|
const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
|
||||||
const sliceThickness = extractSliceThicknessFromDataset(dataSet, frameIndex);
|
const sliceThickness = extractSliceThicknessFromDataset(
|
||||||
|
dataSet,
|
||||||
|
frameIndex
|
||||||
|
);
|
||||||
|
|
||||||
const modality = dataSet.string("x00080060");
|
const modality = dataSet.string("x00080060");
|
||||||
if (modality && modality.includes('NM') && parsedImageId.frame !== undefined && parsedImageId.frame > 1) {
|
if (
|
||||||
|
modality &&
|
||||||
|
modality.includes("NM") &&
|
||||||
|
parsedImageId.frame !== undefined &&
|
||||||
|
parsedImageId.frame > 1
|
||||||
|
) {
|
||||||
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
|
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
|
||||||
const step = spacingBetweenSlices !== undefined ? spacingBetweenSlices : sliceThickness;
|
const step =
|
||||||
if (imageOrientationPatient && imagePositionPatient && step !== undefined && frameIndex !== undefined) {
|
spacingBetweenSlices !== undefined
|
||||||
|
? spacingBetweenSlices
|
||||||
|
: sliceThickness;
|
||||||
|
if (
|
||||||
|
imageOrientationPatient &&
|
||||||
|
imagePositionPatient &&
|
||||||
|
step !== undefined &&
|
||||||
|
frameIndex !== undefined
|
||||||
|
) {
|
||||||
const rowCosines = [
|
const rowCosines = [
|
||||||
parseFloat(imageOrientationPatient[0]),
|
parseFloat(imageOrientationPatient[0]),
|
||||||
parseFloat(imageOrientationPatient[1]),
|
parseFloat(imageOrientationPatient[1]),
|
||||||
|
|
@ -210,7 +413,11 @@ function metaDataProvider(type, imageId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2);
|
const estimatedRadiographicMagnificationFactor = getNumberValues(
|
||||||
|
dataSet,
|
||||||
|
"x00181114",
|
||||||
|
2
|
||||||
|
);
|
||||||
let columnPixelSpacing = null;
|
let columnPixelSpacing = null;
|
||||||
|
|
||||||
let rowPixelSpacing = null;
|
let rowPixelSpacing = null;
|
||||||
|
|
@ -219,8 +426,10 @@ function metaDataProvider(type, imageId) {
|
||||||
rowPixelSpacing = pixelSpacing[0];
|
rowPixelSpacing = pixelSpacing[0];
|
||||||
columnPixelSpacing = pixelSpacing[1];
|
columnPixelSpacing = pixelSpacing[1];
|
||||||
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
|
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
|
||||||
rowPixelSpacing = imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
|
rowPixelSpacing =
|
||||||
columnPixelSpacing = imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
|
imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
|
||||||
|
columnPixelSpacing =
|
||||||
|
imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
|
||||||
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
|
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
|
||||||
rowPixelSpacing = imagePixelSpacing[0];
|
rowPixelSpacing = imagePixelSpacing[0];
|
||||||
columnPixelSpacing = imagePixelSpacing[1];
|
columnPixelSpacing = imagePixelSpacing[1];
|
||||||
|
|
@ -244,37 +453,37 @@ function metaDataProvider(type, imageId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
frameOfReferenceUID: dataSet.string('x00200052'),
|
frameOfReferenceUID: dataSet.string("x00200052"),
|
||||||
rows: dataSet.uint16('x00280010'),
|
rows: dataSet.uint16("x00280010"),
|
||||||
columns: dataSet.uint16('x00280011'),
|
columns: dataSet.uint16("x00280011"),
|
||||||
imageOrientationPatient,
|
imageOrientationPatient,
|
||||||
rowCosines,
|
rowCosines,
|
||||||
columnCosines,
|
columnCosines,
|
||||||
imagePositionPatient,
|
imagePositionPatient,
|
||||||
sliceThickness,
|
sliceThickness,
|
||||||
sliceLocation: dataSet.floatString('x00201041'),
|
sliceLocation: dataSet.floatString("x00201041"),
|
||||||
pixelSpacing,
|
pixelSpacing,
|
||||||
rowPixelSpacing,
|
rowPixelSpacing,
|
||||||
columnPixelSpacing,
|
columnPixelSpacing,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (type === 'imagePixelModule') {
|
if (type === "imagePixelModule") {
|
||||||
const imagePixelModule = {
|
const imagePixelModule = {
|
||||||
samplesPerPixel: dataSet.uint16('x00280002'),
|
samplesPerPixel: dataSet.uint16("x00280002"),
|
||||||
photometricInterpretation: dataSet.string('x00280004'),
|
photometricInterpretation: dataSet.string("x00280004"),
|
||||||
rows: dataSet.uint16('x00280010'),
|
rows: dataSet.uint16("x00280010"),
|
||||||
columns: dataSet.uint16('x00280011'),
|
columns: dataSet.uint16("x00280011"),
|
||||||
bitsAllocated: dataSet.uint16('x00280100'),
|
bitsAllocated: dataSet.uint16("x00280100"),
|
||||||
bitsStored: dataSet.uint16('x00280101'),
|
bitsStored: dataSet.uint16("x00280101"),
|
||||||
highBit: dataSet.uint16('x00280102'),
|
highBit: dataSet.uint16("x00280102"),
|
||||||
pixelRepresentation: dataSet.uint16('x00280103'),
|
pixelRepresentation: dataSet.uint16("x00280103"),
|
||||||
planarConfiguration: dataSet.uint16('x00280006'),
|
planarConfiguration: dataSet.uint16("x00280006"),
|
||||||
pixelAspectRatio: dataSet.string('x00280034'),
|
pixelAspectRatio: dataSet.string("x00280034"),
|
||||||
};
|
};
|
||||||
populateSmallestLargestPixelValues(dataSet, imagePixelModule);
|
populateSmallestLargestPixelValues(dataSet, imagePixelModule);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
imagePixelModule.photometricInterpretation === 'PALETTE COLOR' &&
|
imagePixelModule.photometricInterpretation === "PALETTE COLOR" &&
|
||||||
dataSet.elements.x00281101
|
dataSet.elements.x00281101
|
||||||
) {
|
) {
|
||||||
populatePaletteColorLut(dataSet, imagePixelModule);
|
populatePaletteColorLut(dataSet, imagePixelModule);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// PT 临床数据缓存:用于统一 SUV 口径(优先接口,缺失则回退 DICOM 元数据)
|
||||||
|
const instanceIdToClinicalData = new Map()
|
||||||
|
|
||||||
|
function normalizeId(id) {
|
||||||
|
if (id === undefined || id === null) return null
|
||||||
|
const s = String(id).trim()
|
||||||
|
return s ? s : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setPTClinicalDataForInstance(instanceId, clinicalData) {
|
||||||
|
const key = normalizeId(instanceId)
|
||||||
|
if (!key) return
|
||||||
|
if (!clinicalData || typeof clinicalData !== 'object') return
|
||||||
|
instanceIdToClinicalData.set(key, { ...clinicalData })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPTClinicalDataForInstance(instanceId) {
|
||||||
|
const key = normalizeId(instanceId)
|
||||||
|
if (!key) return null
|
||||||
|
return instanceIdToClinicalData.get(key) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePTClinicalDataForInstance(instanceId) {
|
||||||
|
const key = normalizeId(instanceId)
|
||||||
|
if (!key) return
|
||||||
|
instanceIdToClinicalData.delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearPTClinicalDataCache() {
|
||||||
|
instanceIdToClinicalData.clear()
|
||||||
|
}
|
||||||
|
|
@ -313,6 +313,10 @@ export default {
|
||||||
isExistsClinicalData: {
|
isExistsClinicalData: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
imageToolType: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -549,9 +553,9 @@ export default {
|
||||||
this.signVisible = false
|
this.signVisible = false
|
||||||
// window.location.reload()
|
// window.location.reload()
|
||||||
// window.opener.postMessage('refreshTaskList', window.location)
|
// window.opener.postMessage('refreshTaskList', window.location)
|
||||||
// 设置当前任务阅片状态为已读
|
// 设置当前任务阅片状态为已读
|
||||||
this.adInfo.ReadingTaskState = 2
|
this.adInfo.ReadingTaskState = 2
|
||||||
const res = await getAutoCutNextTask()
|
const res = await getAutoCutNextTask({imageToolType: this.imageToolType})
|
||||||
var isAutoTask = res.Result.AutoCutNextTask
|
var isAutoTask = res.Result.AutoCutNextTask
|
||||||
if (isAutoTask) {
|
if (isAutoTask) {
|
||||||
// store.dispatch('reading/resetVisitTasks')
|
// store.dispatch('reading/resetVisitTasks')
|
||||||
|
|
|
||||||
|
|
@ -1134,7 +1134,8 @@ export default {
|
||||||
// resolve()
|
// resolve()
|
||||||
// })
|
// })
|
||||||
this.loading = true
|
this.loading = true
|
||||||
cornerstone.metaData.addProvider(metaDataProvider, 1);
|
cornerstone.metaData.removeProvider(metaDataProvider)
|
||||||
|
cornerstone.metaData.addProvider(metaDataProvider, 100000)
|
||||||
cornerstone.loadAndCacheImage(this.stack.imageIds[this.stack.currentImageIdIndex])
|
cornerstone.loadAndCacheImage(this.stack.imageIds[this.stack.currentImageIdIndex])
|
||||||
.then(async image => {
|
.then(async image => {
|
||||||
if (this.stack.imageIds.indexOf(image.imageId) !== -1) {
|
if (this.stack.imageIds.indexOf(image.imageId) !== -1) {
|
||||||
|
|
|
||||||
|
|
@ -539,7 +539,7 @@
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<!-- 其他 -->
|
<!-- 其他 -->
|
||||||
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
|
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
|
||||||
<Others v-if="activeName === '3'" />
|
<Others v-if="activeName === '3'" :imageToolType="1"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,38 @@
|
||||||
import { metaData } from '@cornerstonejs/core'
|
import { metaData } from '@cornerstonejs/core'
|
||||||
// import { InstanceMetadata } from '@cornerstonejs/calculate-suv'
|
// import { InstanceMetadata } from '@cornerstonejs/calculate-suv'
|
||||||
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
|
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
|
||||||
|
import { getPTClinicalDataForInstance } from '@/utils/ptClinicalDataCache'
|
||||||
|
|
||||||
function parseImageId(imageId) {
|
function parseImageId(imageId) {
|
||||||
// build a url by parsing out the url scheme and frame index from the imageId
|
|
||||||
const firstColonIndex = imageId.indexOf(':')
|
const firstColonIndex = imageId.indexOf(':')
|
||||||
|
const urlPart = imageId.substring(firstColonIndex + 1)
|
||||||
let url = imageId.substring(firstColonIndex + 1)
|
let url = urlPart
|
||||||
const frameIndex = url.indexOf('frame=')
|
|
||||||
|
|
||||||
let frame
|
let frame
|
||||||
|
const qIndex = urlPart.indexOf('?')
|
||||||
|
|
||||||
if (frameIndex !== -1) {
|
if (qIndex !== -1) {
|
||||||
const frameStr = url.substr(frameIndex + 6)
|
const base = urlPart.slice(0, qIndex)
|
||||||
|
const query = urlPart.slice(qIndex + 1)
|
||||||
|
const parts = query.split('&').filter(Boolean)
|
||||||
|
const preservedParts = []
|
||||||
|
|
||||||
frame = parseInt(frameStr, 10)
|
parts.forEach((part) => {
|
||||||
url = url.substr(0, frameIndex - 1)
|
const eqIndex = part.indexOf('=')
|
||||||
|
const key = eqIndex === -1 ? part : part.slice(0, eqIndex)
|
||||||
|
const value = eqIndex === -1 ? '' : part.slice(eqIndex + 1)
|
||||||
|
|
||||||
|
if (key === 'frame') {
|
||||||
|
const n = value !== '' ? parseInt(value, 10) : NaN
|
||||||
|
if (Number.isFinite(n)) {
|
||||||
|
frame = n
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preservedParts.push(part)
|
||||||
|
})
|
||||||
|
|
||||||
|
url = preservedParts.length ? `${base}?${preservedParts.join('&')}` : base
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -23,6 +41,7 @@ function parseImageId(imageId) {
|
||||||
frame
|
frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMetaData(type, imageId) {
|
function getMetaData(type, imageId) {
|
||||||
// const { dicomParser } = cornerstoneDICOMImageLoader.external
|
// const { dicomParser } = cornerstoneDICOMImageLoader.external
|
||||||
const parsedImageId = parseImageId(imageId)
|
const parsedImageId = parseImageId(imageId)
|
||||||
|
|
@ -32,6 +51,7 @@ function getMetaData(type, imageId) {
|
||||||
if (!dataSet) {
|
if (!dataSet) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'petImageModule') {
|
if (type === 'petImageModule') {
|
||||||
// 1340137.4196974 240000
|
// 1340137.4196974 240000
|
||||||
// console.log(dataSet.string('x00541300'), dataSet.string('x00181242'))
|
// console.log(dataSet.string('x00541300'), dataSet.string('x00181242'))
|
||||||
|
|
@ -41,173 +61,69 @@ function getMetaData(type, imageId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getPTImageIdInstanceMetadata(imageId) {
|
export default function getPTImageIdInstanceMetadata(imageId) {
|
||||||
|
const instanceId = getInstanceIdFromImageId(imageId)
|
||||||
|
const ptClinicalData = instanceId ? getPTClinicalDataForInstance(instanceId) : null
|
||||||
const petSequenceModule = metaData.get('petIsotopeModule', imageId)
|
const petSequenceModule = metaData.get('petIsotopeModule', imageId)
|
||||||
|
const generalSeriesModule = metaData.get('generalSeriesModule', imageId) || {}
|
||||||
|
const patientStudyModule = metaData.get('patientStudyModule', imageId) || {}
|
||||||
|
const ptSeriesModule = metaData.get('petSeriesModule', imageId) || {}
|
||||||
|
const ptImageModule = getMetaData('petImageModule', imageId) || {}
|
||||||
|
const radiopharmaceuticalInfo = petSequenceModule?.radiopharmaceuticalInfo
|
||||||
|
|
||||||
const generalSeriesModule = metaData.get('generalSeriesModule', imageId)
|
if (!radiopharmaceuticalInfo) {
|
||||||
const patientStudyModule = metaData.get('patientStudyModule', imageId)
|
|
||||||
|
|
||||||
const ptSeriesModule = metaData.get('petSeriesModule', imageId)
|
|
||||||
// const ptImageModule = metaData.get('petImageModule', imageId)
|
|
||||||
|
|
||||||
const ptImageModule = getMetaData('petImageModule', imageId)
|
|
||||||
if (!petSequenceModule) {
|
|
||||||
throw new Error('petSequenceModule metadata is required')
|
throw new Error('petSequenceModule metadata is required')
|
||||||
}
|
}
|
||||||
|
|
||||||
const radiopharmaceuticalInfo = petSequenceModule.radiopharmaceuticalInfo
|
const patientWeight = toNumber(
|
||||||
|
ptClinicalData?.PatientWeight,
|
||||||
const { seriesDate, seriesTime, acquisitionDate, acquisitionTime } =
|
patientStudyModule.patientWeight
|
||||||
generalSeriesModule
|
)
|
||||||
var { patientWeight } = patientStudyModule
|
const totalDose = toNumber(
|
||||||
// console.log('更改前:', patientWeight)
|
ptClinicalData?.RadionuclideTotalDose,
|
||||||
// patientWeight = patientWeight * 10
|
radiopharmaceuticalInfo.radionuclideTotalDose
|
||||||
// console.log('更改后:', patientWeight)
|
)
|
||||||
const { correctedImage, units, decayCorrection } = ptSeriesModule
|
const halfLife = toNumber(
|
||||||
|
ptClinicalData?.RadionuclideHalfLife,
|
||||||
if (
|
radiopharmaceuticalInfo.radionuclideHalfLife
|
||||||
seriesDate === undefined ||
|
)
|
||||||
seriesTime === undefined ||
|
const startTime = firstValue(
|
||||||
patientWeight === undefined ||
|
normalizeDicomTime(ptClinicalData?.RadiopharmaceuticalStartTime),
|
||||||
acquisitionDate === undefined ||
|
toDicomTimeString(radiopharmaceuticalInfo.radiopharmaceuticalStartTime)
|
||||||
acquisitionTime === undefined ||
|
)
|
||||||
correctedImage === undefined ||
|
const acquisitionTime = firstValue(
|
||||||
units === undefined ||
|
normalizeDicomTime(ptClinicalData?.AcquisitionTime),
|
||||||
decayCorrection === undefined ||
|
toDicomTimeString(generalSeriesModule.acquisitionTime)
|
||||||
radiopharmaceuticalInfo.radionuclideTotalDose === undefined ||
|
)
|
||||||
radiopharmaceuticalInfo.radionuclideHalfLife === undefined ||
|
|
||||||
(radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime === undefined &&
|
|
||||||
seriesDate === undefined &&
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime === undefined)
|
|
||||||
//
|
|
||||||
) {
|
|
||||||
throw new Error('required metadata are missing')
|
|
||||||
}
|
|
||||||
|
|
||||||
const instanceMetadata = {
|
const instanceMetadata = {
|
||||||
CorrectedImage: correctedImage,
|
CorrectedImage: ptSeriesModule.correctedImage,
|
||||||
Units: units,
|
Units: ptSeriesModule.units,
|
||||||
RadionuclideHalfLife: radiopharmaceuticalInfo.radionuclideHalfLife,
|
RadionuclideHalfLife: halfLife,
|
||||||
RadionuclideTotalDose: radiopharmaceuticalInfo.radionuclideTotalDose,
|
RadionuclideTotalDose: totalDose,
|
||||||
DecayCorrection: decayCorrection,
|
DecayCorrection: ptSeriesModule.decayCorrection,
|
||||||
PatientWeight: patientWeight,
|
PatientWeight: patientWeight,
|
||||||
SeriesDate: seriesDate,
|
SeriesDate: toDicomDateString(generalSeriesModule.seriesDate),
|
||||||
SeriesTime: seriesTime,
|
SeriesTime: toDicomTimeString(generalSeriesModule.seriesTime),
|
||||||
AcquisitionDate: acquisitionDate,
|
AcquisitionDate: toDicomDateString(generalSeriesModule.acquisitionDate),
|
||||||
AcquisitionTime: acquisitionTime
|
AcquisitionTime: acquisitionTime
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
assignIfPresent(
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime &&
|
instanceMetadata,
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== undefined &&
|
'RadiopharmaceuticalStartDateTime',
|
||||||
typeof radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime === 'string'
|
toDicomDateString(radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime)
|
||||||
) {
|
)
|
||||||
instanceMetadata.RadiopharmaceuticalStartDateTime =
|
assignIfPresent(instanceMetadata, 'RadiopharmaceuticalStartTime', startTime)
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime
|
assignIfPresent(instanceMetadata, 'FrameReferenceTime', ptImageModule.frameReferenceTime)
|
||||||
}
|
assignIfPresent(instanceMetadata, 'ActualFrameDuration', ptImageModule.actualFrameDuration)
|
||||||
|
assignIfPresent(
|
||||||
if (
|
instanceMetadata,
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime &&
|
'PatientSex',
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== undefined &&
|
firstValue(ptClinicalData?.PatientSex, patientStudyModule.patientSex)
|
||||||
typeof radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== 'string'
|
)
|
||||||
) {
|
assignIfPresent(instanceMetadata, 'PatientSize', patientStudyModule.patientSize)
|
||||||
const dateString = convertInterfaceDateToString(
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime
|
|
||||||
)
|
|
||||||
instanceMetadata.RadiopharmaceuticalStartDateTime = dateString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
instanceMetadata.AcquisitionDate &&
|
|
||||||
instanceMetadata.AcquisitionDate !== undefined &&
|
|
||||||
typeof instanceMetadata.AcquisitionDate !== 'string'
|
|
||||||
) {
|
|
||||||
const dateString = convertInterfaceDateToString(
|
|
||||||
instanceMetadata.AcquisitionDate
|
|
||||||
)
|
|
||||||
instanceMetadata.AcquisitionDate = dateString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
instanceMetadata.SeriesDate &&
|
|
||||||
instanceMetadata.SeriesDate !== undefined &&
|
|
||||||
typeof instanceMetadata.SeriesDate !== 'string'
|
|
||||||
) {
|
|
||||||
const dateString = convertInterfaceDateToString(
|
|
||||||
instanceMetadata.SeriesDate
|
|
||||||
)
|
|
||||||
instanceMetadata.SeriesDate = dateString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime &&
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== undefined &&
|
|
||||||
typeof radiopharmaceuticalInfo.radiopharmaceuticalStartTime === 'string'
|
|
||||||
) {
|
|
||||||
instanceMetadata.RadiopharmaceuticalStartTime =
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime &&
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== undefined &&
|
|
||||||
typeof radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== 'string'
|
|
||||||
) {
|
|
||||||
const timeString = convertInterfaceTimeToString(
|
|
||||||
radiopharmaceuticalInfo.radiopharmaceuticalStartTime
|
|
||||||
)
|
|
||||||
instanceMetadata.RadiopharmaceuticalStartTime = timeString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
instanceMetadata.AcquisitionTime &&
|
|
||||||
instanceMetadata.AcquisitionTime !== undefined &&
|
|
||||||
typeof instanceMetadata.AcquisitionTime !== 'string'
|
|
||||||
) {
|
|
||||||
const timeString = convertInterfaceTimeToString(
|
|
||||||
instanceMetadata.AcquisitionTime
|
|
||||||
)
|
|
||||||
instanceMetadata.AcquisitionTime = timeString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
instanceMetadata.SeriesTime &&
|
|
||||||
instanceMetadata.SeriesTime !== undefined &&
|
|
||||||
typeof instanceMetadata.SeriesTime !== 'string'
|
|
||||||
) {
|
|
||||||
const timeString = convertInterfaceTimeToString(
|
|
||||||
instanceMetadata.SeriesTime
|
|
||||||
)
|
|
||||||
instanceMetadata.SeriesTime = timeString
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
ptImageModule.frameReferenceTime &&
|
|
||||||
ptImageModule.frameReferenceTime !== undefined
|
|
||||||
) {
|
|
||||||
instanceMetadata.FrameReferenceTime = ptImageModule.frameReferenceTime
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
ptImageModule.actualFrameDuration &&
|
|
||||||
ptImageModule.actualFrameDuration !== undefined
|
|
||||||
) {
|
|
||||||
instanceMetadata.ActualFrameDuration = ptImageModule.actualFrameDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
patientStudyModule.patientSex &&
|
|
||||||
patientStudyModule.patientSex !== undefined
|
|
||||||
) {
|
|
||||||
instanceMetadata.PatientSex = patientStudyModule.patientSex
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
patientStudyModule.patientSize &&
|
|
||||||
patientStudyModule.patientSize !== undefined
|
|
||||||
) {
|
|
||||||
instanceMetadata.PatientSize = patientStudyModule.patientSize
|
|
||||||
}
|
|
||||||
|
|
||||||
return instanceMetadata
|
return instanceMetadata
|
||||||
}
|
}
|
||||||
|
|
@ -222,15 +138,81 @@ function convertInterfaceTimeToString(time) {
|
||||||
'0'
|
'0'
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeString = `${hours}${minutes}${seconds}.${fractionalSeconds}`
|
return `${hours}${minutes}${seconds}.${fractionalSeconds}`
|
||||||
return timeString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertInterfaceDateToString(date) {
|
function convertInterfaceDateToString(date) {
|
||||||
const month = `${date.month}`.padStart(2, '0')
|
const month = `${date.month}`.padStart(2, '0')
|
||||||
const day = `${date.day}`.padStart(2, '0')
|
const day = `${date.day}`.padStart(2, '0')
|
||||||
const dateString = `${date.year}${month}${day}`
|
return `${date.year}${month}${day}`
|
||||||
return dateString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getPTImageIdInstanceMetadata }
|
export { getPTImageIdInstanceMetadata }
|
||||||
|
|
||||||
|
function getInstanceIdFromImageId(imageId) {
|
||||||
|
try {
|
||||||
|
const qIndex = imageId.indexOf('?')
|
||||||
|
if (qIndex === -1) return null
|
||||||
|
const params = new URLSearchParams(imageId.slice(qIndex + 1))
|
||||||
|
const instanceId = params.get('instanceId')
|
||||||
|
if (!instanceId) return null
|
||||||
|
return String(instanceId).trim() || null
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDicomTime(value) {
|
||||||
|
if (!hasValue(value)) return null
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return convertInterfaceTimeToString(value)
|
||||||
|
}
|
||||||
|
const raw = String(value).trim()
|
||||||
|
if (!raw) return null
|
||||||
|
|
||||||
|
if (raw.includes(':')) {
|
||||||
|
const parts = raw.split(':')
|
||||||
|
const hh = `${parts[0] || '00'}`.padStart(2, '0')
|
||||||
|
const mm = `${parts[1] || '00'}`.padStart(2, '0')
|
||||||
|
const ss = `${parts[2] || '00'}`.padStart(2, '0')
|
||||||
|
return `${hh}${mm}${ss}.000000`
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleaned = raw.replace(/[^\d.]/g, '')
|
||||||
|
if (!cleaned) return null
|
||||||
|
const [baseRaw, fracRaw] = cleaned.split('.')
|
||||||
|
const base = `${baseRaw || ''}`.padStart(6, '0').slice(-6)
|
||||||
|
const frac = `${fracRaw || ''}`.padEnd(6, '0').slice(0, 6)
|
||||||
|
return `${base}.${frac}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasValue(value) {
|
||||||
|
return value !== undefined && value !== null && value !== ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstValue(...values) {
|
||||||
|
return values.find(hasValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(...values) {
|
||||||
|
const value = firstValue(...values)
|
||||||
|
if (!hasValue(value)) return value
|
||||||
|
|
||||||
|
const numberValue = Number(value)
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : value
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDicomTimeString(value) {
|
||||||
|
if (!hasValue(value)) return undefined
|
||||||
|
return typeof value === 'string' ? value : convertInterfaceTimeToString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDicomDateString(value) {
|
||||||
|
if (!hasValue(value)) return undefined
|
||||||
|
return typeof value === 'string' ? value : convertInterfaceDateToString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignIfPresent(target, key, value) {
|
||||||
|
if (!hasValue(value)) return
|
||||||
|
target[key] = value
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,31 @@
|
||||||
import { utilities as csUtils } from '@cornerstonejs/core'
|
import { utilities as csUtils } from '@cornerstonejs/core'
|
||||||
|
|
||||||
const scalingPerImageId = {}
|
const scalingPerImageId = {}
|
||||||
|
|
||||||
function addInstance(imageId, scalingMetaData) {
|
function normalizeImageURI(imageURI) {
|
||||||
const imageURI = csUtils.imageIdToURI(imageId)
|
if (!imageURI) return imageURI
|
||||||
scalingPerImageId[imageURI] = scalingMetaData
|
const qIndex = imageURI.indexOf('?')
|
||||||
}
|
if (qIndex === -1) return imageURI
|
||||||
|
const base = imageURI.slice(0, qIndex)
|
||||||
function get(type, imageId) {
|
const query = imageURI.slice(qIndex + 1)
|
||||||
if (type === 'scalingModule') {
|
const params = new URLSearchParams(query)
|
||||||
const imageURI = csUtils.imageIdToURI(imageId)
|
if (!params.has('frame')) return imageURI
|
||||||
return scalingPerImageId[imageURI]
|
params.delete('frame')
|
||||||
}
|
const rest = params.toString()
|
||||||
}
|
return rest ? `${base}?${rest}` : base
|
||||||
|
}
|
||||||
export default { addInstance, get }
|
|
||||||
|
function addInstance(imageId, scalingMetaData) {
|
||||||
|
// 统一缩放元数据的 key:忽略 frame 参数,避免 getCurrentImageId() 带 frame 导致查不到 scalingModule
|
||||||
|
const imageURI = normalizeImageURI(csUtils.imageIdToURI(imageId))
|
||||||
|
scalingPerImageId[imageURI] = scalingMetaData
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(type, imageId) {
|
||||||
|
if (type === 'scalingModule') {
|
||||||
|
const imageURI = normalizeImageURI(csUtils.imageIdToURI(imageId))
|
||||||
|
return scalingPerImageId[imageURI]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { addInstance, get }
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,37 @@
|
||||||
<el-tooltip class="item" effect="dark" :content="series.description" placement="right">
|
<el-tooltip class="item" effect="dark" :content="series.description" placement="right">
|
||||||
<div style="">{{ series.description }}</div>
|
<div style="">{{ series.description }}</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<div class="patient-info" style="position: absolute;right: 0;top: 30px;" v-if="['PT','PET'].includes(series.modality)">
|
||||||
|
<el-popover placement="right" trigger="hover" popper-class="patient-info-popper">
|
||||||
|
<h4>{{ $t('trials:ptData:title') }}</h4>
|
||||||
|
<div class="patient-info-row">
|
||||||
|
<label>{{ $t('trials:ptData:label:patientSex') }}</label>
|
||||||
|
<span>{{ study.PatientSex }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="patient-info-row">
|
||||||
|
<label>{{ $t('trials:ptData:label:patientWeight') }}</label>
|
||||||
|
<span>{{ study.PatientWeight }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="patient-info-row">
|
||||||
|
<label>{{ $t('trials:ptData:label:totalDose') }}</label>
|
||||||
|
<span>{{ study.RadionuclideTotalDose }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="patient-info-row">
|
||||||
|
<label>{{ $t('trials:ptData:label:halfLife') }}</label>
|
||||||
|
<span>{{ study.RadionuclideHalfLife }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="patient-info-row">
|
||||||
|
<label>{{ $t('trials:ptData:label:injectTime') }}</label>
|
||||||
|
<span>{{ study.RadiopharmaceuticalStartTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="patient-info-row">
|
||||||
|
<label>{{ $t('trials:ptData:label:acquisitionTime') }}</label>
|
||||||
|
<span>{{ study.AcquisitionTime }}</span>
|
||||||
|
</div>
|
||||||
|
<i slot="reference" class="el-icon-document"
|
||||||
|
style="font-size: 15px;cursor: pointer;color: #f5f7fa;" />
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-show="series.sliceThickness && !study.IsCriticalSequence">
|
<p v-show="series.sliceThickness && !study.IsCriticalSequence">
|
||||||
|
|
@ -1108,3 +1139,46 @@ export default {
|
||||||
background-color: #213a54;
|
background-color: #213a54;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.patient-info-popper {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ddd;
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
border-color: #5a5a5a;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info-popper[x-placement^='right'] .popper__arrow {
|
||||||
|
border-right-color: #5a5a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info-popper[x-placement^='right'] .popper__arrow::after {
|
||||||
|
border-right-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info-popper .patient-info-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 100px minmax(0, 1fr);
|
||||||
|
column-gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info-popper .patient-info-row + .patient-info-row {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info-popper label {
|
||||||
|
color: #bbb;
|
||||||
|
// font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info-popper span {
|
||||||
|
text-align: left;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1081,7 +1081,8 @@ export default {
|
||||||
// resolve()
|
// resolve()
|
||||||
// })
|
// })
|
||||||
this.loading = true
|
this.loading = true
|
||||||
cornerstone.metaData.addProvider(metaDataProvider, 1);
|
cornerstone.metaData.removeProvider(metaDataProvider)
|
||||||
|
cornerstone.metaData.addProvider(metaDataProvider, 100000)
|
||||||
cornerstone.loadAndCacheImage(this.stack.imageIds[this.stack.currentImageIdIndex])
|
cornerstone.loadAndCacheImage(this.stack.imageIds[this.stack.currentImageIdIndex])
|
||||||
.then(async image => {
|
.then(async image => {
|
||||||
if (this.stack.imageIds.indexOf(image.imageId) !== -1) {
|
if (this.stack.imageIds.indexOf(image.imageId) !== -1) {
|
||||||
|
|
|
||||||
|
|
@ -577,6 +577,7 @@ import colorMap from './colorMap.vue'
|
||||||
import RectangleROITool from './tools/RectangleROITool'
|
import RectangleROITool from './tools/RectangleROITool'
|
||||||
import ScaleOverlayTool from './tools/ScaleOverlayTool'
|
import ScaleOverlayTool from './tools/ScaleOverlayTool'
|
||||||
import SegmentBidirectionalTool from './tools/SegmentBidirectionalTool'
|
import SegmentBidirectionalTool from './tools/SegmentBidirectionalTool'
|
||||||
|
import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache'
|
||||||
import FixedRadiusCircleROITool from './tools/FixedRadiusCircleROITool'
|
import FixedRadiusCircleROITool from './tools/FixedRadiusCircleROITool'
|
||||||
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
|
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
|
||||||
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
|
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
|
||||||
|
|
@ -1155,10 +1156,32 @@ export default {
|
||||||
let keySeriesIndex = -1
|
let keySeriesIndex = -1
|
||||||
const arr = res1.Result
|
const arr = res1.Result
|
||||||
arr.forEach((study, studyIndex) => {
|
arr.forEach((study, studyIndex) => {
|
||||||
|
// 仅对 PT/PET study 缓存临床参数,供 3D SUV 计算链路覆盖原始 DICOM 元数据
|
||||||
|
const ptClinicalData = {
|
||||||
|
PatientSex: study.PatientSex,
|
||||||
|
PatientWeight: study.PatientWeight,
|
||||||
|
RadionuclideTotalDose: study.RadionuclideTotalDose,
|
||||||
|
RadionuclideHalfLife: study.RadionuclideHalfLife,
|
||||||
|
RadiopharmaceuticalStartTime: study.RadiopharmaceuticalStartTime,
|
||||||
|
AcquisitionTime: study.AcquisitionTime
|
||||||
|
}
|
||||||
|
const isPtStudy = ['PT、CT', 'CT、PT', 'PET-CT'].includes(study.Modalities)
|
||||||
|
const hasPtClinicalData =
|
||||||
|
isPtStudy &&
|
||||||
|
(
|
||||||
|
ptClinicalData.PatientWeight !== null ||
|
||||||
|
ptClinicalData.RadionuclideTotalDose !== null ||
|
||||||
|
ptClinicalData.RadionuclideHalfLife !== null ||
|
||||||
|
ptClinicalData.RadiopharmaceuticalStartTime !== null ||
|
||||||
|
ptClinicalData.AcquisitionTime !== null
|
||||||
|
)
|
||||||
study.SeriesList.forEach((series, seriesIndex) => {
|
study.SeriesList.forEach((series, seriesIndex) => {
|
||||||
const imageIds = []
|
const imageIds = []
|
||||||
const stack = []
|
const stack = []
|
||||||
series.InstanceInfoList.forEach((instance, instanceIndex) => {
|
series.InstanceInfoList.forEach((instance, instanceIndex) => {
|
||||||
|
if (hasPtClinicalData && ['PT', 'PET'].includes(String(series.Modality).toUpperCase())) {
|
||||||
|
setPTClinicalDataForInstance(instance.Id, ptClinicalData)
|
||||||
|
}
|
||||||
if (study.IsCriticalSequence) {
|
if (study.IsCriticalSequence) {
|
||||||
keyStudyIndex = studyIndex
|
keyStudyIndex = studyIndex
|
||||||
keySeriesIndex = seriesIndex
|
keySeriesIndex = seriesIndex
|
||||||
|
|
@ -4404,6 +4427,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
clearPTClinicalDataCache()
|
||||||
DicomEvent.$off('isCanActiveNoneDicomTool')
|
DicomEvent.$off('isCanActiveNoneDicomTool')
|
||||||
DicomEvent.$off('removeNoneDicomMeasureData')
|
DicomEvent.$off('removeNoneDicomMeasureData')
|
||||||
DicomEvent.$off('addNoneDicomMeasureData')
|
DicomEvent.$off('addNoneDicomMeasureData')
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="series.Description" class="text-desc" :title="series.Description" style="position: relative;">
|
<div v-if="series.Description" class="text-desc" :title="series.Description" style="position: relative;">
|
||||||
{{ series.Description }}
|
{{ series.Description }}
|
||||||
<div class="patient-info" style="position: absolute;right: 0;top: 0;" v-if="taskInfo && taskInfo.IsExistsClinicalData && ['PT','PET'].includes(series.Modality)">
|
<div class="patient-info" style="position: absolute;right: 0;top: 0;" v-if="['PT','PET'].includes(series.Modality)">
|
||||||
<el-popover placement="right" trigger="hover" popper-class="patient-info-popper">
|
<el-popover placement="right" trigger="hover" popper-class="patient-info-popper">
|
||||||
<h4>{{ $t('trials:ptData:title') }}</h4>
|
<h4>{{ $t('trials:ptData:title') }}</h4>
|
||||||
<div class="patient-info-row">
|
<div class="patient-info-row">
|
||||||
|
|
@ -104,7 +104,7 @@
|
||||||
<span>{{ study.AcquisitionTime }}</span>
|
<span>{{ study.AcquisitionTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
<i slot="reference" class="el-icon-document"
|
<i slot="reference" class="el-icon-document"
|
||||||
style="font-size: 15px;cursor: pointer;color: #428bca;" />
|
style="font-size: 15px;cursor: pointer;color: #f5f7fa;" />
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<!-- 其他 -->
|
<!-- 其他 -->
|
||||||
<el-tab-pane :label="$t('trials:reading:tab:others')" name="2">
|
<el-tab-pane :label="$t('trials:reading:tab:others')" name="2">
|
||||||
<Others v-if="activeName === '2'" />
|
<Others v-if="activeName === '2'" :imageToolType="2"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,7 @@
|
||||||
v-model="formData.PatientSex"
|
v-model="formData.PatientSex"
|
||||||
:placeholder="$t('common:ruleMessage:select')"
|
:placeholder="$t('common:ruleMessage:select')"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:disabled="!allowAddOrEdit"
|
||||||
>
|
>
|
||||||
<el-option :label="$t('trials:patientSex:male')" value="M"></el-option>
|
<el-option :label="$t('trials:patientSex:male')" value="M"></el-option>
|
||||||
<el-option :label="$t('trials:patientSex:female')" value="F"></el-option>
|
<el-option :label="$t('trials:patientSex:female')" value="F"></el-option>
|
||||||
|
|
@ -251,6 +252,7 @@
|
||||||
v-model.number="formData.PatientWeight"
|
v-model.number="formData.PatientWeight"
|
||||||
:placeholder="$t('trials:patientWeight:eg')"
|
:placeholder="$t('trials:patientWeight:eg')"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:disabled="!allowAddOrEdit"
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -261,6 +263,7 @@
|
||||||
v-model.number="formData.RadionuclideTotalDose"
|
v-model.number="formData.RadionuclideTotalDose"
|
||||||
:placeholder="$t('trials:totalDose:eg')"
|
:placeholder="$t('trials:totalDose:eg')"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:disabled="!allowAddOrEdit"
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 半衰期(s) 例如 21600-->
|
<!-- 半衰期(s) 例如 21600-->
|
||||||
|
|
@ -269,6 +272,7 @@
|
||||||
v-model.number="formData.RadionuclideHalfLife"
|
v-model.number="formData.RadionuclideHalfLife"
|
||||||
:placeholder="$t('trials:halfLife:eg')"
|
:placeholder="$t('trials:halfLife:eg')"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:disabled="!allowAddOrEdit"
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -280,6 +284,7 @@
|
||||||
:placeholder="$t('trials:injectTime:eg')"
|
:placeholder="$t('trials:injectTime:eg')"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@input="computeTimeRelation"
|
@input="computeTimeRelation"
|
||||||
|
:disabled="!allowAddOrEdit"
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 成像时间(s) Unix 秒 或 相对秒-->
|
<!-- 成像时间(s) Unix 秒 或 相对秒-->
|
||||||
|
|
@ -289,6 +294,7 @@
|
||||||
:placeholder="$t('trials:injectTime:eg')"
|
:placeholder="$t('trials:injectTime:eg')"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@input="computeTimeRelation"
|
@input="computeTimeRelation"
|
||||||
|
:disabled="!allowAddOrEdit"
|
||||||
></el-input>
|
></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -303,7 +309,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 提交 -->
|
<!-- 提交 -->
|
||||||
<el-form-item style="margin-top: 20px;text-align: right;">
|
<el-form-item style="margin-top: 20px;text-align: right;" v-if="allowAddOrEdit">
|
||||||
<el-button type="primary" @click="submitForm">{{ $t('trials:ptData:button:submit') }}</el-button>
|
<el-button type="primary" @click="submitForm">{{ $t('trials:ptData:button:submit') }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
|
||||||
|
|
@ -2452,7 +2452,7 @@ export default {
|
||||||
const routeData = this.$router.resolve({
|
const routeData = this.$router.resolve({
|
||||||
path: `/showdicom?trialId=${this.trialId}&subjectVisitId=${this.data.Id
|
path: `/showdicom?trialId=${this.trialId}&subjectVisitId=${this.data.Id
|
||||||
}&studyId=${row.StudyId}&showDelete=${this.isAudit ? 0 : 1
|
}&studyId=${row.StudyId}&showDelete=${this.isAudit ? 0 : 1
|
||||||
}&TokenKey=${token}&type=Study&isHaveStudyClinicalData=${this.IsHaveStudyClinicalData}`,
|
}&TokenKey=${token}&type=Study`,
|
||||||
})
|
})
|
||||||
this.open = window.open(routeData.href, '_blank')
|
this.open = window.open(routeData.href, '_blank')
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue