Merge branch 'main' into uat_us
# Conflicts: # src/views/trials/trials-layout/components/trialsNavbar.vueuat_us
|
|
@ -1,4 +1,39 @@
|
|||
|
||||
kind: pipeline
|
||||
type: ssh
|
||||
name: ssh-linux-uat-irc-publish
|
||||
|
||||
platform:
|
||||
os: Linux
|
||||
arch: 386
|
||||
|
||||
clone:
|
||||
disable: true #禁用默认克隆
|
||||
|
||||
server:
|
||||
host: 101.132.253.119
|
||||
user: root
|
||||
password:
|
||||
from_secret: local_pwd
|
||||
|
||||
steps:
|
||||
- name: publish-uat-irc-web
|
||||
commands:
|
||||
- bash /opt/1panel/xc-deploy-new/devops-center/build-vue-then-publish.sh IRC_Web uat --mode uat
|
||||
- name: notify-wecom
|
||||
commands:
|
||||
- echo $DRONE_COMMIT_AUTHOR "$DRONE_COMMIT_MESSAGE"
|
||||
- bash /opt/1panel/xc-deploy-new/devops-center/drone-notify-wecom.sh "$DRONE_BUILD_STATUS" "$DRONE_REPO_NAME" "$DRONE_BRANCH" "$DRONE_BUILD_NUMBER" "4355b98e-1e72-4678-8dfb-2fc6ad0bf449" "$DRONE_COMMIT_MESSAGE" "$DRONE_COMMIT_AUTHOR" "Uat_IRC_WEB" "irc.uat.extimaging.com"
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
trigger:
|
||||
branch:
|
||||
- uat
|
||||
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: ssh
|
||||
name: ssh-linux-test-irc-publish
|
||||
|
|
@ -30,9 +65,6 @@ steps:
|
|||
- failure
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
|
||||
|
||||
- main
|
||||
|
||||
|
||||
19
package.json
|
|
@ -16,18 +16,17 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.701.0",
|
||||
"@cornerstonejs/adapters": "^2.19.7",
|
||||
"@cornerstonejs/adapters": "4.19.2",
|
||||
"@cornerstonejs/calculate-suv": "^1.1.0",
|
||||
"@cornerstonejs/core": "^2.19.7",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.19.7",
|
||||
"@cornerstonejs/tools": "^2.19.7",
|
||||
"@cornerstonejs/core": "^4.19.2",
|
||||
"@cornerstonejs/dicom-image-loader": "^4.19.2",
|
||||
"@cornerstonejs/tools": "^4.19.2",
|
||||
"@fingerprintjs/fingerprintjs": "^4.6.2",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@riophae/vue-treeselect": "^0.4.0",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue/composition-api": "^1.7.2",
|
||||
"ali-oss": "^6.17.1",
|
||||
"axios": "^0.18.1",
|
||||
"core-js": "^3.8.3",
|
||||
|
|
@ -61,11 +60,10 @@
|
|||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.5",
|
||||
"streamsaver": "^2.0.6",
|
||||
"svg-sprite-loader": "^4.1.3",
|
||||
"svgo": "^1.2.2",
|
||||
"v-viewer": "^1.7.4",
|
||||
"vcrontab": "^0.3.5",
|
||||
"vue": "^2.6.14",
|
||||
"vue": "2.7.16",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-contextmenujs": "^1.4.11",
|
||||
"vue-count-to": "^1.0.13",
|
||||
|
|
@ -99,9 +97,10 @@
|
|||
"path-browserify": "^1.0.1",
|
||||
"process": "^0.11.10",
|
||||
"sass": "^1.63.2",
|
||||
"sass-loader": "^10.4.1",
|
||||
"sass-loader": "^16.0.7",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"vue-template-compiler": "2.7.16",
|
||||
"webpack": "^5.96.1",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
|
|
@ -126,4 +125,4 @@
|
|||
"not dead",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -115,8 +115,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
// this.show = process.env.VUE_APP_OSS_PATH === '/test/dist'
|
||||
this.show = false
|
||||
this.show = process.env.VUE_APP_OSS_PATH === '/test/dist'
|
||||
Vue.prototype.$openI18n = this.openI18n
|
||||
},
|
||||
// watch: {
|
||||
|
|
|
|||
|
|
@ -319,3 +319,92 @@ export function getReportsChartData(param) {
|
|||
data: param
|
||||
})
|
||||
}
|
||||
|
||||
// 分割相关
|
||||
// 获取分割组
|
||||
export function getSegmentationList(param) {
|
||||
return request({
|
||||
url: `/Segmentation/getSegmentationList`,
|
||||
method: 'post',
|
||||
data: param
|
||||
})
|
||||
}
|
||||
// 新增修改分割组
|
||||
export function addOrUpdateSegmentation(param) {
|
||||
return request({
|
||||
url: `/Segmentation/addOrUpdateSegmentation`,
|
||||
method: 'post',
|
||||
data: param
|
||||
})
|
||||
}
|
||||
// 删除分割组
|
||||
export function deleteSegmentation(segmentationId) {
|
||||
return request({
|
||||
url: `/Segmentation/deleteSegmentation/${segmentationId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
// 获取分割
|
||||
export function getSegmentList(param) {
|
||||
return request({
|
||||
url: `/Segmentation/getSegmentList`,
|
||||
method: 'post',
|
||||
data: param
|
||||
})
|
||||
}
|
||||
// 新增修改分割
|
||||
export function addOrUpdateSegment(param) {
|
||||
return request({
|
||||
url: `/Segmentation/addOrUpdateSegment`,
|
||||
method: 'post',
|
||||
data: param
|
||||
})
|
||||
}
|
||||
// 删除分割
|
||||
export function deleteSegment(segmentId) {
|
||||
return request({
|
||||
url: `/Segmentation/deleteSegment/${segmentId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
// 获取分割绑定关系
|
||||
export function getSegmentBindingList(param) {
|
||||
return request({
|
||||
url: `/Segmentation/getSegmentBindingList`,
|
||||
method: 'post',
|
||||
data: param
|
||||
})
|
||||
}
|
||||
// 保存分割绑定关系(可用作修改删除)
|
||||
export function saveSegmentBindingAndAnswer(param) {
|
||||
return request({
|
||||
url: `/Segmentation/saveSegmentBindingAndAnswer`,
|
||||
method: 'post',
|
||||
data: param
|
||||
})
|
||||
}
|
||||
|
||||
// 获取表格问题配置
|
||||
export function getReadingTableQuestionTrialById(params) {
|
||||
return request({
|
||||
url: `/ReadingQuestion/getReadingTableQuestionTrialById`,
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 获取外层问题配置
|
||||
export function getReadingQuestionTrialById(params) {
|
||||
return request({
|
||||
url: `/ReadingQuestion/getReadingQuestionTrialById`,
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 修改分割组保存状态
|
||||
export function changeSegmentationSavedStatus(data) {
|
||||
return request({
|
||||
url: `/Segmentation/changeSegmentationSavedStatus`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -74,10 +74,11 @@ export default {
|
|||
zzSessionStorage.setItem('lastWorkbench', `${v.path}${query ? '?' : ''}${query}`)
|
||||
var firstGoIn = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children[0]
|
||||
if (this.trialsTab === '/trials/trials-panel') {
|
||||
this.$router.replace({ path: `${firstGoIn.path}${query ? '?' : ''}${query}` })
|
||||
var targetPath = firstGoIn.path
|
||||
if (firstGoIn.children && firstGoIn.children.length > 0) {
|
||||
this.$router.replace({ path: `${firstGoIn.children[0].path}${query ? '?' : ''}${query}` })
|
||||
targetPath = firstGoIn.children[0].path
|
||||
}
|
||||
this.$router.replace({ path: `${targetPath}${query ? '?' : ''}${query}` })
|
||||
}
|
||||
// document.cookie = 'TrialId=' + this.$route.query.trialId + ';path=/'
|
||||
},
|
||||
|
|
@ -112,10 +113,11 @@ export default {
|
|||
this.getTrialList()
|
||||
var firstGoIn = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children[0]
|
||||
if (this.trialsTab === '/trials/trials-panel') {
|
||||
this.$router.replace({ path: `${firstGoIn.path}${query ? '?' : ''}${query}` })
|
||||
var targetPath = firstGoIn.path
|
||||
if (firstGoIn.children && firstGoIn.children.length > 0) {
|
||||
this.$router.replace({ path: `${firstGoIn.children[0].path}${query ? '?' : ''}${query}` })
|
||||
targetPath = firstGoIn.children[0].path
|
||||
}
|
||||
this.$router.replace({ path: `${targetPath}${query ? '?' : ''}${query}` })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -201,15 +203,19 @@ export default {
|
|||
if (~url.indexOf('?')) {
|
||||
query = url.split('?')[1]
|
||||
}
|
||||
this.$router.push({ path: `${v.name}${query ? '?' : ''}${query}` })
|
||||
var trialsPanelList = this.trialsRouter.children.find(v => { return v.name === 'TrialsPanel' }).children
|
||||
var isHasChild = trialsPanelList.find(c => { return c.path === v.name }) && trialsPanelList.find(c => { return c.path === v.name }).children
|
||||
if (trialsPanelList.find(c => { return c.path === v.name }) && trialsPanelList.find(c => { return c.path === v.name }).tabHiddn) {
|
||||
var trialsPanelList = this.trialsRouter.children.find(item => { return item.name === 'TrialsPanel' }).children
|
||||
var currentRoute = trialsPanelList.find(c => { return c.path === v.name })
|
||||
|
||||
if (currentRoute && currentRoute.tabHiddn) {
|
||||
this.$router.push({ path: `${v.name}${query ? '?' : ''}${query}` })
|
||||
return
|
||||
}
|
||||
var isHasChild = currentRoute && currentRoute.children
|
||||
if (isHasChild && isHasChild.length > 0) {
|
||||
this.trialsTabChild = isHasChild[0].path
|
||||
this.$router.push({ path: `${this.trialsTabChild}${query ? '?' : ''}${query}` })
|
||||
} else {
|
||||
this.$router.push({ path: `${v.name}${query ? '?' : ''}${query}` })
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ export default {
|
|||
display: none !important; /* 隐藏原生 radio 输入,但仍然允许交互 */
|
||||
}
|
||||
|
||||
::v-deep.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled)
|
||||
::v-deep .el-radio:focus:not(.is-focus):not(:active):not(.is-disabled)
|
||||
.el-radio__inner {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -712,12 +712,12 @@ export default {
|
|||
var studyUid = data.string('x0020000d')
|
||||
if (!studyUid) return resolve()
|
||||
var pixelDataElement = data.elements.x7fe00010
|
||||
if (!pixelDataElement && modality !== 'SR') return resolve()
|
||||
if (!pixelDataElement && modality !== 'SR' && modality !== 'ECG') return resolve()
|
||||
var studyIndex = 0
|
||||
while (
|
||||
studyIndex < scope.uploadQueues.length &&
|
||||
scope.uploadQueues[studyIndex].dicomInfo.studyUid !== studyUid &&
|
||||
(pixelDataElement || modality === 'SR') &&
|
||||
(pixelDataElement || modality === 'SR' || modality === 'ECG') &&
|
||||
modality != ''
|
||||
) {
|
||||
++studyIndex
|
||||
|
|
|
|||
|
|
@ -86,16 +86,16 @@ export default {
|
|||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.el-tabs--left .el-tabs__header.is-left {
|
||||
::v-deep .el-tabs--left .el-tabs__header.is-left {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.uploadDicomAndNonedicom {
|
||||
::v-deep.el-tabs--border-card>.el-tabs__header .el-tabs__item {
|
||||
::v-deep .el-tabs--border-card>.el-tabs__header .el-tabs__item {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
::v-deep.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active {
|
||||
::v-deep .el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active {
|
||||
color: #428bca;
|
||||
background-color: #fff;
|
||||
border-right-color: #dcdfe6;
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<svg width="24px" height="24px" viewBox="0 0 24 24" class="h-6 w-6"><g id="tool-seg-brush" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><rect id="Rectangle" x="0" y="0" width="24" height="24"></rect><path d="M3.24640621,21.8286833 C3.09173375,21.7551472 2.99513748,21.5971513 3.00018895,21.4259625 C3.00524042,21.2547737 3.11098486,21.1027485 3.26972426,21.0384606 C5.3260304,20.2059201 4.66362518,17.8620247 5.27421252,16.6088957 C6.02197747,15.1026514 7.84114758,14.4766383 9.35746132,15.2037675 C13.9485253,17.4422999 8.48346644,24.3211232 3.24640621,21.8286833 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M21.5968442,2.302022 C21.1256527,1.8826075 20.4094461,1.90228142 19.9619901,2.34693083 L9.69255027,12.5887345 C11.0536437,13.0051578 12.1843437,13.9616637 12.8206229,15.2349008 L21.7410706,3.93687606 C22.1347378,3.44008272 22.0714081,2.72222021 21.5968442,2.302022 Z" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,12 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="h-6 w-6" color="#fff">
|
||||
<path
|
||||
d="M10.3945 12.6113C10.043 12.6113 9.78516 12.3945 9.78516 12.0371C9.78516 11.6855 10.043 11.4688 10.3945 11.4688H13.2012C13.5527 11.4688 13.8164 11.6855 13.8164 12.0371C13.8164 12.3945 13.5527 12.6113 13.2012 12.6113H10.3945Z"
|
||||
fill="currentColor"></path>
|
||||
<path opacity="0.5"
|
||||
d="M18.312 6.84473C21.8473 8.88583 22.9225 13.6424 20.7134 17.4687C18.5042 21.2951 13.8473 22.7422 10.312 20.7011C9.51661 20.2419 8.84737 19.6439 8.31042 18.9512C11.3012 19.3598 14.4827 17.8251 16.1946 14.8598C17.9067 11.8945 17.6457 8.3712 15.7963 5.98538C16.6645 6.10409 17.5167 6.38556 18.312 6.84473Z"
|
||||
fill="currentColor"></path>
|
||||
<circle cx="13.7852" cy="13.4688" r="8" stroke="currentColor"></circle>
|
||||
<path
|
||||
d="M4.50586 8.22266C4.14844 8.22266 3.92578 7.9707 3.92578 7.60742V5.68555H2.10352C1.74609 5.68555 1.5 5.46875 1.5 5.11719C1.5 4.76562 1.74609 4.54297 2.10352 4.54297H3.92578V2.61523C3.92578 2.25195 4.14844 2 4.50586 2C4.85742 2 5.08008 2.25195 5.08008 2.61523V4.54297H6.89648C7.25391 4.54297 7.50586 4.76562 7.50586 5.11719C7.50586 5.46875 7.25391 5.68555 6.89648 5.68555H5.08008V7.60742C5.08008 7.9707 4.85742 8.22266 4.50586 8.22266Z"
|
||||
fill="currentColor"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg width="18px" height="18px" viewBox="0 0 18 18" class="text-primary"><g id="view-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Group-13"><rect id="Rectangle" x="0" y="0" width="18" height="18"></rect><rect id="Rectangle" fill="currentColor" x="2" y="2" width="14" height="14" rx="1"></rect></g></g></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
|
|
@ -0,0 +1 @@
|
|||
<svg width="18px" height="18px" viewBox="0 0 18 18" class="text-primary"><g id="view-outline-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Group-13"><rect id="Rectangle" x="0" y="0" width="18" height="18"></rect><rect id="Rectangle" stroke="currentColor" x="1.5" y="1.5" width="15" height="15" rx="1"></rect><rect id="Rectangle" fill="currentColor" x="3.5" y="3.5" width="11" height="11" rx="1"></rect></g></g></svg>
|
||||
|
After Width: | Height: | Size: 446 B |
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1774331916694" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1627" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M198.4 960A134.4 134.4 0 0 1 64 825.6V198.4A134.4 134.4 0 0 1 198.4 64h268.8a44.8 44.8 0 0 1 44.8 44.8 44.8 44.8 0 0 1-44.8 44.8H198.4a44.8 44.8 0 0 0-44.8 44.8v627.2a44.8 44.8 0 0 0 44.8 44.8h627.2a44.8 44.8 0 0 0 44.8-44.8V556.8a44.8 44.8 0 0 1 44.8-44.8 44.8 44.8 0 0 1 44.8 44.8v268.8A134.4 134.4 0 0 1 825.6 960z m281.984-416.384a44.8 44.8 0 0 1 0-63.296L807.232 153.6H646.464a44.8 44.8 0 0 1-44.8-44.8 44.8 44.8 0 0 1 44.8-44.8h267.264a44.8 44.8 0 0 1 28.288 8.896l1.408 1.152 0.64 0.512 1.024 0.96a41.216 41.216 0 0 1 3.136 3.072l0.896 1.088 0.512 0.64 1.152 1.344a44.8 44.8 0 0 1 8.832 28.288V377.28a44.8 44.8 0 0 1-44.8 44.8 44.8 44.8 0 0 1-44.8-44.8V216.512l-326.4 326.4a44.8 44.8 0 0 1-32 13.248A44.8 44.8 0 0 1 480 543.616z" p-id="1628" fill="#2c2c2c"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg width="18px" height="18px" viewBox="0 0 18 18" class="text-primary"><g id="view-outline" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Group-13"><rect id="Rectangle" x="0" y="0" width="18" height="18"></rect><rect id="Rectangle" stroke="currentColor" x="1.5" y="1.5" width="15" height="15" rx="1"></rect></g></g></svg>
|
||||
|
After Width: | Height: | Size: 347 B |
|
|
@ -0,0 +1 @@
|
|||
<svg width="24px" height="24px" viewBox="0 0 24 24" class="h-6 w-6"><g id="tool-seg-threshold" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><rect id="Rectangle" x="0" y="0" width="24" height="24"></rect><g id="Group" transform="translate(2.4184, 14.3673)" stroke="currentColor" stroke-width="1.5"><line x1="16.5816327" y1="2.13265306" x2="19.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><line x1="0.0816326531" y1="2.13265306" x2="12.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><circle id="Oval" cx="14.2244898" cy="2.09183673" r="2.09183673"></circle></g><g id="Group" transform="translate(11.7092, 7.4592) scale(-1, 1) translate(-11.7092, -7.4592)translate(1.9184, 5.3673)" stroke="currentColor" stroke-width="1.5"><line x1="16" y1="2.13265306" x2="19.0816327" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><line x1="0.0816326531" y1="2.13265306" x2="11" y2="2.13265306" id="Line-2" stroke-linecap="round"></line><circle id="Oval" cx="13.2244898" cy="2.09183673" r="2.09183673"></circle></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -56,8 +56,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/mixin.scss";
|
||||
@import "~@/styles/variables.module.scss";
|
||||
@use "@/styles/mixin.scss" as *;
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
// @import "~@/styles/mixin.scss";
|
||||
// @import "~@/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
|
|
|
|||
|
|
@ -156,12 +156,6 @@ export const constantRoutes = [
|
|||
hidden: true,
|
||||
component: () => import('@/views/trials/trials-panel/reading/dicoms/components/Fusion/PetCt')
|
||||
},
|
||||
{
|
||||
path: '/fusion',
|
||||
name: 'fusion',
|
||||
hidden: true,
|
||||
component: () => import('@/views/trials/trials-panel/reading/dicoms/components/Fusion/demo/index')
|
||||
},
|
||||
|
||||
{
|
||||
path: '/historyScreenshot',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
@import "./variables.module.scss";
|
||||
@import "./mixin.scss";
|
||||
@import "./transition.scss";
|
||||
@import "./element-ui.scss";
|
||||
@import "./sidebar.scss";
|
||||
@use "./variables.module.scss";
|
||||
@use "./mixin.scss";
|
||||
@use "./transition.scss";
|
||||
@use "./element-ui.scss";
|
||||
@use "./sidebar.scss";
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
// font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
// font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
||||
// "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial,
|
||||
|
|
@ -205,133 +206,139 @@ body .el-table th.gutter {
|
|||
.el-descriptions {
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
color: #303133
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.el-descriptions__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-descriptions__title {
|
||||
font-size: 16px;
|
||||
font-weight: 700
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.el-descriptions__body {
|
||||
color: #606266;
|
||||
background-color: #fff
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.el-descriptions__body .el-descriptions__table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
table-layout: fixed
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell {
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
font-weight: 400;
|
||||
line-height: 1.5
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-left {
|
||||
text-align: left
|
||||
.el-descriptions__body
|
||||
.el-descriptions__table
|
||||
.el-descriptions-item__cell.is-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-center {
|
||||
text-align: center
|
||||
.el-descriptions__body
|
||||
.el-descriptions__table
|
||||
.el-descriptions-item__cell.is-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-descriptions__body .el-descriptions__table .el-descriptions-item__cell.is-right {
|
||||
text-align: right
|
||||
.el-descriptions__body
|
||||
.el-descriptions__table
|
||||
.el-descriptions-item__cell.is-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.el-descriptions .is-bordered {
|
||||
table-layout: auto
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.el-descriptions .is-bordered .el-descriptions-item__cell {
|
||||
border: 1px solid #ebeef5;
|
||||
padding: 12px 10px
|
||||
padding: 12px 10px;
|
||||
}
|
||||
|
||||
.el-descriptions :not(.is-bordered) .el-descriptions-item__cell {
|
||||
padding-bottom: 12px
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.el-descriptions--medium.is-bordered .el-descriptions-item__cell {
|
||||
padding: 10px
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.el-descriptions--medium:not(.is-bordered) .el-descriptions-item__cell {
|
||||
padding-bottom: 10px
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.el-descriptions--small {
|
||||
font-size: 12px
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-descriptions--small.is-bordered .el-descriptions-item__cell {
|
||||
padding: 8px 10px
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.el-descriptions--small:not(.is-bordered) .el-descriptions-item__cell {
|
||||
padding-bottom: 8px
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.el-descriptions--mini {
|
||||
font-size: 12px
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-descriptions--mini.is-bordered .el-descriptions-item__cell {
|
||||
padding: 6px 10px
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.el-descriptions--mini:not(.is-bordered) .el-descriptions-item__cell {
|
||||
padding-bottom: 6px
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.el-descriptions-item {
|
||||
vertical-align: top
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.el-descriptions-item__container {
|
||||
display: flex
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.el-descriptions-item__container .el-descriptions-item__content,
|
||||
.el-descriptions-item__container .el-descriptions-item__label {
|
||||
display: inline-flex;
|
||||
align-items: baseline
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.el-descriptions-item__container .el-descriptions-item__content {
|
||||
flex: 1
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-descriptions-item__label.has-colon:after {
|
||||
content: ":";
|
||||
position: relative;
|
||||
top: -.5px
|
||||
top: -0.5px;
|
||||
}
|
||||
|
||||
.el-descriptions-item__label.is-bordered-label {
|
||||
font-weight: 700;
|
||||
color: #909399;
|
||||
background: #fafafa
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.el-descriptions-item__label:not(.is-bordered-label) {
|
||||
margin-right: 10px
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.el-descriptions-item__content {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.feedBack-box {
|
||||
|
|
@ -350,7 +357,7 @@ body .el-table th.gutter {
|
|||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
background: #000;
|
||||
z-index: 3999;
|
||||
}
|
||||
|
|
@ -371,4 +378,4 @@ body .el-table th.gutter {
|
|||
height: 20px !important;
|
||||
vertical-align: -0.4em !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8366,7 +8366,7 @@
|
|||
|
||||
for (var i = 0; i < valarr.length; i++) {
|
||||
var checkValue = valarr[i],
|
||||
checklen = lengths[i],
|
||||
checklen = lengths[i] || 0,
|
||||
isString = false,
|
||||
displaylen = checklen;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,343 @@
|
|||
import getNumberValues from "./getNumberValues";
|
||||
import isNMReconstructable from "./isNMReconstructable";
|
||||
|
||||
/**
|
||||
* Get a subpart of Image Type dicom tag defined by index
|
||||
* @param {*} dataSet
|
||||
* @param {*} index 0 based index of the subtype
|
||||
*/
|
||||
function getImageTypeSubItemFromDataset(dataSet, index) {
|
||||
const imageType = dataSet.string("x00080008");
|
||||
|
||||
if (imageType) {
|
||||
const subTypes = imageType.split("\\");
|
||||
|
||||
if (subTypes.length > index) {
|
||||
return subTypes[index];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
// DICOM 标准中针对 Enhanced CT/MR/PET 等“增强型多帧”(Enhanced Multi-frame)影像, 强制要求为每一帧显式记录真实的独立空间坐标 。
|
||||
|
||||
// - 它们的数据通常存放在: Per-frame Functional Groups Sequence (5200,9230) -> 对应的 Frame Item -> Plane Position Sequence (0020,9113) -> ImagePositionPatient (0020,0032) 。
|
||||
function getSharedFunctionalGroupsDataSet(dataSet) {
|
||||
const seq = dataSet?.elements?.x52009229;
|
||||
if (!seq?.items?.length) {
|
||||
return;
|
||||
}
|
||||
return seq.items[0].dataSet;
|
||||
}
|
||||
|
||||
function getPerFrameFunctionalGroupsDataSet(dataSet, frameIndex) {
|
||||
const seq = dataSet?.elements?.x52009230;
|
||||
if (!seq?.items?.length) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!Number.isInteger(frameIndex) ||
|
||||
frameIndex < 0 ||
|
||||
frameIndex >= seq.items.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return seq.items[frameIndex].dataSet;
|
||||
}
|
||||
|
||||
function getOrientationFromPlaneOrientationSequence(dataSet) {
|
||||
const seq = dataSet?.elements?.x00209116;
|
||||
if (!seq?.items?.length) {
|
||||
return;
|
||||
}
|
||||
return getNumberValues(seq.items[0].dataSet, "x00200037", 6);
|
||||
}
|
||||
|
||||
function getPositionFromPlanePositionSequence(dataSet) {
|
||||
const seq = dataSet?.elements?.x00209113;
|
||||
if (!seq?.items?.length) {
|
||||
return;
|
||||
}
|
||||
return getNumberValues(seq.items[0].dataSet, "x00200032", 3);
|
||||
}
|
||||
|
||||
function getPixelSpacingFromPixelMeasuresSequence(dataSet) {
|
||||
const seq = dataSet?.elements?.x00289110;
|
||||
if (!seq?.items?.length) {
|
||||
return;
|
||||
}
|
||||
return getNumberValues(seq.items[0].dataSet, "x00280030", 2);
|
||||
}
|
||||
|
||||
function getSliceThicknessFromPixelMeasuresSequence(dataSet) {
|
||||
const seq = dataSet?.elements?.x00289110;
|
||||
if (!seq?.items?.length) {
|
||||
return;
|
||||
}
|
||||
if (!seq.items[0]?.dataSet?.elements?.x00180050) {
|
||||
return;
|
||||
}
|
||||
return seq.items[0].dataSet.floatString("x00180050");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the orientation from NM multiframe dataset, if image type
|
||||
* equal to RECON TOMO or RECON GATED TOMO
|
||||
* @param {*} dataSet
|
||||
* @returns
|
||||
*/
|
||||
function extractOrientationFromNMMultiframeDataset(dataSet) {
|
||||
let imageOrientationPatient;
|
||||
const modality = dataSet.string("x00080060");
|
||||
|
||||
if (modality?.includes("NM")) {
|
||||
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
|
||||
|
||||
if (imageSubType && isNMReconstructable(imageSubType)) {
|
||||
if (dataSet.elements.x00540022) {
|
||||
imageOrientationPatient = getNumberValues(
|
||||
dataSet.elements.x00540022.items[0].dataSet,
|
||||
"x00200037",
|
||||
6
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imageOrientationPatient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the position from NM multiframe dataset, if image type
|
||||
* equal to RECON TOMO or RECON GATED TOMO
|
||||
* @param {*} dataSet
|
||||
* @returns
|
||||
*/
|
||||
function extractPositionFromNMMultiframeDataset(dataSet) {
|
||||
let imagePositionPatient;
|
||||
const modality = dataSet.string("x00080060");
|
||||
|
||||
if (modality?.includes("NM")) {
|
||||
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
|
||||
|
||||
if (imageSubType && isNMReconstructable(imageSubType)) {
|
||||
if (dataSet.elements.x00540022) {
|
||||
imagePositionPatient = getNumberValues(
|
||||
dataSet.elements.x00540022.items[0].dataSet,
|
||||
"x00200032",
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imagePositionPatient;
|
||||
}
|
||||
|
||||
function isMultiFrame(dataSet) {
|
||||
const numberOfFrames =
|
||||
dataSet.string("x00280008") || dataSet.uint16("x00280008");
|
||||
return numberOfFrames !== undefined && Number(numberOfFrames) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract orientation information from a dataset. It tries to get the orientation
|
||||
* from the Detector Information Sequence (for NM images) if image type equal
|
||||
* to RECON TOMO or RECON GATED TOMO
|
||||
* @param {*} dataSet
|
||||
* @returns
|
||||
*/
|
||||
function extractOrientationFromDataset(dataSet) {
|
||||
// let imageOrientationPatient;
|
||||
|
||||
// if (isMultiFrame(dataSet)) {
|
||||
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
|
||||
// dataSet,
|
||||
// frameIndex
|
||||
// );
|
||||
// imageOrientationPatient =
|
||||
// getOrientationFromPlaneOrientationSequence(perFrameDataSet);
|
||||
|
||||
// if (!imageOrientationPatient) {
|
||||
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
|
||||
// imageOrientationPatient =
|
||||
// getOrientationFromPlaneOrientationSequence(sharedDataSet);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!imageOrientationPatient) {
|
||||
// imageOrientationPatient = getNumberValues(dataSet, "x00200037", 6);
|
||||
// }
|
||||
let imageOrientationPatient = getNumberValues(dataSet, "x00200037", 6)
|
||||
// Trying to get the orientation from the Plane Orientation Sequence
|
||||
if (!imageOrientationPatient && dataSet.elements.x00209116) {
|
||||
imageOrientationPatient = getNumberValues(
|
||||
dataSet.elements.x00209116.items[0].dataSet,
|
||||
"x00200037",
|
||||
6
|
||||
);
|
||||
}
|
||||
|
||||
// If orientation not valid to this point, trying to get the orientation
|
||||
// from the Detector Information Sequence (for NM images) with image type
|
||||
// equal to RECON TOMO or RECON GATED TOMO
|
||||
|
||||
if (!imageOrientationPatient) {
|
||||
imageOrientationPatient =
|
||||
extractOrientationFromNMMultiframeDataset(dataSet);
|
||||
}
|
||||
|
||||
return imageOrientationPatient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract position information from a dataset. It tries to get the position
|
||||
* from the Detector Information Sequence (for NM images) if image type equal
|
||||
* to RECON TOMO or RECON GATED TOMO
|
||||
* @param {*} dataSet
|
||||
* @returns
|
||||
*/
|
||||
function extractPositionFromDataset(dataSet) {
|
||||
// let imagePositionPatient;
|
||||
|
||||
// if (isMultiFrame(dataSet)) {
|
||||
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
|
||||
// dataSet,
|
||||
// frameIndex
|
||||
// );
|
||||
// imagePositionPatient =
|
||||
// getPositionFromPlanePositionSequence(perFrameDataSet);
|
||||
|
||||
// if (!imagePositionPatient) {
|
||||
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
|
||||
// imagePositionPatient =
|
||||
// getPositionFromPlanePositionSequence(sharedDataSet);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!imagePositionPatient) {
|
||||
// imagePositionPatient = getNumberValues(dataSet, "x00200032", 3);
|
||||
// }
|
||||
let imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||
// Trying to get the position from the Plane Position Sequence
|
||||
if (!imagePositionPatient && dataSet.elements.x00209113) {
|
||||
imagePositionPatient = getNumberValues(
|
||||
dataSet.elements.x00209113.items[0].dataSet,
|
||||
"x00200032",
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
// If position not valid to this point, trying to get the position
|
||||
// from the Detector Information Sequence (for NM images)
|
||||
if (!imagePositionPatient) {
|
||||
imagePositionPatient = extractPositionFromNMMultiframeDataset(dataSet);
|
||||
}
|
||||
|
||||
return imagePositionPatient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the pixelSpacing information. If exists, extracts this information
|
||||
* from Pixel Measures Sequence
|
||||
* @param {*} dataSet
|
||||
* @returns
|
||||
*/
|
||||
function extractSpacingFromDataset(dataSet) {
|
||||
// let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
||||
|
||||
// // If pixelSpacing not valid to this point, trying to get the spacing
|
||||
// // from the Pixel Measures Sequence
|
||||
// if (!pixelSpacing && dataSet.elements.x00289110) {
|
||||
// pixelSpacing = getNumberValues(
|
||||
// dataSet.elements.x00289110.items[0].dataSet,
|
||||
// 'x00280030',
|
||||
// 2
|
||||
// );
|
||||
// }
|
||||
|
||||
// return pixelSpacing;
|
||||
|
||||
let rowPixelSpacing = null;
|
||||
let columnPixelSpacing = null;
|
||||
// let pixelSpacing;
|
||||
|
||||
// if (isMultiFrame(dataSet)) {
|
||||
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
|
||||
// dataSet,
|
||||
// frameIndex
|
||||
// );
|
||||
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
|
||||
// pixelSpacing =
|
||||
// getPixelSpacingFromPixelMeasuresSequence(perFrameDataSet) ||
|
||||
// getPixelSpacingFromPixelMeasuresSequence(sharedDataSet);
|
||||
// }
|
||||
|
||||
// if (!pixelSpacing) {
|
||||
// pixelSpacing =
|
||||
// getNumberValues(dataSet, "x00280030", 2) ||
|
||||
// getPixelSpacingFromPixelMeasuresSequence(dataSet);
|
||||
// }
|
||||
let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
||||
const imagePixelSpacing = getNumberValues(dataSet, "x00181164", 2);
|
||||
const estimatedRadiographicMagnificationFactor = getNumberValues(
|
||||
dataSet,
|
||||
"x00181114",
|
||||
2
|
||||
);
|
||||
if (pixelSpacing) {
|
||||
rowPixelSpacing = pixelSpacing[0];
|
||||
columnPixelSpacing = pixelSpacing[1];
|
||||
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
|
||||
rowPixelSpacing =
|
||||
imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
|
||||
columnPixelSpacing =
|
||||
imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
|
||||
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
|
||||
rowPixelSpacing = imagePixelSpacing[0];
|
||||
columnPixelSpacing = imagePixelSpacing[1];
|
||||
}
|
||||
return rowPixelSpacing !== null
|
||||
? [rowPixelSpacing, columnPixelSpacing]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the sliceThickness information. If exists, extracts this information
|
||||
* from Pixel Measures Sequence
|
||||
* @param {*} dataSet
|
||||
* @returns
|
||||
*/
|
||||
function extractSliceThicknessFromDataset(dataSet) {
|
||||
let sliceThickness;
|
||||
if (dataSet.elements.x00180050) {
|
||||
sliceThickness = dataSet.floatString('x00180050');
|
||||
}
|
||||
else if (dataSet.elements.x00289110 &&
|
||||
dataSet.elements.x00289110.items.length &&
|
||||
dataSet.elements.x00289110.items[0].dataSet.elements.x00180050) {
|
||||
sliceThickness =
|
||||
dataSet.elements.x00289110.items[0].dataSet.floatString('x00180050');
|
||||
}
|
||||
|
||||
// if (sliceThickness === undefined && isMultiFrame(dataSet)) {
|
||||
// const perFrameDataSet = getPerFrameFunctionalGroupsDataSet(
|
||||
// dataSet,
|
||||
// frameIndex
|
||||
// );
|
||||
// const sharedDataSet = getSharedFunctionalGroupsDataSet(dataSet);
|
||||
// sliceThickness =
|
||||
// getSliceThicknessFromPixelMeasuresSequence(perFrameDataSet) ||
|
||||
// getSliceThicknessFromPixelMeasuresSequence(sharedDataSet);
|
||||
// }
|
||||
|
||||
return sliceThickness;
|
||||
}
|
||||
|
||||
export {
|
||||
getImageTypeSubItemFromDataset,
|
||||
extractOrientationFromDataset,
|
||||
extractPositionFromDataset,
|
||||
extractSpacingFromDataset,
|
||||
extractSliceThicknessFromDataset,
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
function getNumberValues(
|
||||
dataSet,
|
||||
tag,
|
||||
minimumLength
|
||||
){
|
||||
const values = [];
|
||||
const valueAsString = dataSet.string(tag);
|
||||
|
||||
if (!valueAsString) {
|
||||
return;
|
||||
}
|
||||
const split = valueAsString.split('\\');
|
||||
|
||||
if (minimumLength && split.length < minimumLength) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
values.push(parseFloat(split[i]));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
export default getNumberValues;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default function isNMReconstructable(imageSubType) {
|
||||
return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO';
|
||||
}
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
|
||||
import {
|
||||
getImageTypeSubItemFromDataset,
|
||||
extractOrientationFromDataset,
|
||||
extractPositionFromDataset,
|
||||
extractSpacingFromDataset,
|
||||
extractSliceThicknessFromDataset,
|
||||
} from './extractPositioningFromDataset';
|
||||
function parseImageId(imageId) {
|
||||
// build a url by parsing out the url scheme and frame index from the imageId
|
||||
const firstColonIndex = imageId.indexOf(':');
|
||||
|
|
@ -141,6 +148,19 @@ function populatePaletteColorLut(dataSet, imagePixelModule) {
|
|||
imagePixelModule.bluePaletteColorLookupTableDescriptor
|
||||
);
|
||||
}
|
||||
|
||||
function getSpacingBetweenSlices(dataSet) {
|
||||
if (dataSet?.elements?.x00180088) {
|
||||
return dataSet.floatString('x00180088');
|
||||
}
|
||||
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
|
||||
if (
|
||||
pixelMeasuresSequence?.items?.length &&
|
||||
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
|
||||
) {
|
||||
return pixelMeasuresSequence.items[0].dataSet.floatString('x00180088');
|
||||
}
|
||||
}
|
||||
function metaDataProvider(type, imageId) {
|
||||
const parsedImageId = parseImageId(imageId);
|
||||
const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
|
||||
|
|
@ -149,10 +169,47 @@ function metaDataProvider(type, imageId) {
|
|||
return;
|
||||
}
|
||||
if (type === 'imagePlaneModule') {
|
||||
const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
||||
const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||
const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
||||
// const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
||||
// const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||
// const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
||||
const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2);
|
||||
const frameIndex = parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
|
||||
const imageOrientationPatient = extractOrientationFromDataset(dataSet, frameIndex);
|
||||
let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
|
||||
const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
|
||||
const sliceThickness = extractSliceThicknessFromDataset(dataSet, frameIndex);
|
||||
|
||||
const modality = dataSet.string("x00080060");
|
||||
if (modality && modality.includes('NM') && parsedImageId.frame !== undefined && parsedImageId.frame > 1) {
|
||||
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
|
||||
const step = spacingBetweenSlices !== undefined ? spacingBetweenSlices : sliceThickness;
|
||||
if (imageOrientationPatient && imagePositionPatient && step !== undefined && frameIndex !== undefined) {
|
||||
const rowCosines = [
|
||||
parseFloat(imageOrientationPatient[0]),
|
||||
parseFloat(imageOrientationPatient[1]),
|
||||
parseFloat(imageOrientationPatient[2]),
|
||||
];
|
||||
const columnCosines = [
|
||||
parseFloat(imageOrientationPatient[3]),
|
||||
parseFloat(imageOrientationPatient[4]),
|
||||
parseFloat(imageOrientationPatient[5]),
|
||||
];
|
||||
const normal = [
|
||||
rowCosines[1] * columnCosines[2] - rowCosines[2] * columnCosines[1],
|
||||
rowCosines[2] * columnCosines[0] - rowCosines[0] * columnCosines[2],
|
||||
rowCosines[0] * columnCosines[1] - rowCosines[1] * columnCosines[0],
|
||||
];
|
||||
|
||||
const offset = frameIndex * parseFloat(step);
|
||||
|
||||
imagePositionPatient = [
|
||||
parseFloat(imagePositionPatient[0]) + normal[0] * offset,
|
||||
parseFloat(imagePositionPatient[1]) + normal[1] * offset,
|
||||
parseFloat(imagePositionPatient[2]) + normal[2] * offset,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2);
|
||||
let columnPixelSpacing = null;
|
||||
|
||||
|
|
@ -194,7 +251,7 @@ function metaDataProvider(type, imageId) {
|
|||
rowCosines,
|
||||
columnCosines,
|
||||
imagePositionPatient,
|
||||
sliceThickness: dataSet.floatString('x00180050'),
|
||||
sliceThickness,
|
||||
sliceLocation: dataSet.floatString('x00201041'),
|
||||
pixelSpacing,
|
||||
rowPixelSpacing,
|
||||
|
|
@ -224,6 +281,11 @@ function metaDataProvider(type, imageId) {
|
|||
}
|
||||
return imagePixelModule;
|
||||
}
|
||||
// if (type === "multiframeModule") {
|
||||
// return {
|
||||
// NumberOfFrames: dataSet.uint16("x00280008") || dataSet.string("x00280008")
|
||||
// };
|
||||
// }
|
||||
// if (type === 'imagePixelModule') {
|
||||
// return {
|
||||
// samplesPerPixel: dataSet.uint16('x00280002'),
|
||||
|
|
@ -249,4 +311,4 @@ function metaDataProvider(type, imageId) {
|
|||
// }
|
||||
// }
|
||||
}
|
||||
export default metaDataProvider;
|
||||
export default metaDataProvider;
|
||||
|
|
|
|||
|
|
@ -965,8 +965,8 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.imageBox {
|
||||
<style lang="scss">
|
||||
.viewerContainer .imageBox {
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
|
|
|
|||
|
|
@ -158,108 +158,105 @@
|
|||
<div class="viewerSidethumbinner">
|
||||
|
||||
<el-collapse v-model="relationActiveName" @change="handelRelationActiveChange">
|
||||
<template v-for="item in relationStudyListByVisitName">
|
||||
<div :key="`${item.VisitName}`">
|
||||
<div v-show="item.VisitName" class="text-desc" style="background-color: #1f1f1f;">
|
||||
{{ item.VisitName }}
|
||||
</div>
|
||||
<template v-for="(study, studyIndex) in relationStudyList">
|
||||
<el-collapse-item :key="`${study.StudyId}`" :name="`${study.StudyId}`"
|
||||
v-if="study.VisitName === item.VisitName">
|
||||
<template slot="title">
|
||||
<div v-for="item in relationStudyListByVisitName" :key="`${item.VisitName}`">
|
||||
<div v-show="item.VisitName" class="text-desc" style="background-color: #1f1f1f;">
|
||||
{{ item.VisitName }}
|
||||
</div>
|
||||
<template v-for="(study, studyIndex) in relationStudyList">
|
||||
<el-collapse-item :key="`${study.StudyId}`" :name="`${study.StudyId}`"
|
||||
v-if="study.VisitName === item.VisitName">
|
||||
<template slot="title">
|
||||
|
||||
<div class="text-desc">
|
||||
{{ study.StudyCode }}
|
||||
</div>
|
||||
<!-- <div v-show="study.Description" class="text-desc">
|
||||
{{ study.Description }}
|
||||
</div> -->
|
||||
|
||||
<div v-show="study.SeriesCount" class="text-desc">
|
||||
{{ study.Modalities }} : {{ study.SeriesCount }} Series
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
|
||||
{{ study.Description }}
|
||||
<div class="text-desc">
|
||||
{{ study.StudyCode }}
|
||||
</div>
|
||||
<div class="viewerSidethumbs ps" style="position: relative;">
|
||||
<div class="viewerSidethumbinner">
|
||||
<div v-for="(seriesItem, index) in study.seriesList" :key="seriesItem.seriesId">
|
||||
<div class="viewernavigatorwrapper"
|
||||
style="position: relative;border:1px solid #434343;" series-type="relation"
|
||||
@click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
|
||||
<div class="imageBox" style="width: 72px;height:72px;">
|
||||
<img class="image-preview" :src="seriesItem.previewImageUrl"
|
||||
crossorigin="anonymous" alt="" style="width: 72px;height:72px;" fit="fill" />
|
||||
<i class="el-icon-refresh" :title="$t('tip:refreshImage')"
|
||||
@click.stop="refreshImage(item)"></i>
|
||||
</div>
|
||||
<!-- <div v-show="study.Description" class="text-desc">
|
||||
{{ study.Description }}
|
||||
</div> -->
|
||||
|
||||
<div class="viewernavitextwrapper">
|
||||
<div
|
||||
style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
|
||||
<div v-if="seriesItem.keySeries" style="color:red">
|
||||
Key Images
|
||||
</div>
|
||||
<div v-else>#{{ seriesItem.seriesNumber }}</div>
|
||||
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1">
|
||||
<el-popover placement="right-start" trigger="click"
|
||||
popper-class="instance_frame_wrapper">
|
||||
<div class="frame_list">
|
||||
<div v-for="(instance, idx) in seriesItem.instanceInfoList"
|
||||
:key="instance.Id" class="frame_content"
|
||||
:style="{ 'margin-bottom': idx < seriesItem.instanceInfoList.length - 1 ? '5px' : '0px' }"
|
||||
@click="showMultiFrames(studyIndex, seriesItem, index, instance)">
|
||||
<div>
|
||||
<div>{{ instance.InstanceNumber }}</div>
|
||||
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1}
|
||||
frame`
|
||||
}}</div>
|
||||
</div>
|
||||
<div v-show="study.SeriesCount" class="text-desc">
|
||||
{{ study.Modalities }} : {{ study.SeriesCount }} Series
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
|
||||
{{ study.Description }}
|
||||
</div>
|
||||
<div class="viewerSidethumbs ps" style="position: relative;">
|
||||
<div class="viewerSidethumbinner">
|
||||
<div v-for="(seriesItem, index) in study.seriesList" :key="seriesItem.seriesId">
|
||||
<div class="viewernavigatorwrapper"
|
||||
style="position: relative;border:1px solid #434343;" series-type="relation"
|
||||
@click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
|
||||
<div class="imageBox" style="width: 72px;height:72px;">
|
||||
<img class="image-preview" :src="seriesItem.previewImageUrl"
|
||||
crossorigin="anonymous" alt="" style="width: 72px;height:72px;" fit="fill" />
|
||||
<i class="el-icon-refresh" :title="$t('tip:refreshImage')"
|
||||
@click.stop="refreshImage(item)"></i>
|
||||
</div>
|
||||
|
||||
<div class="viewernavitextwrapper">
|
||||
<div
|
||||
style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
|
||||
<div v-if="seriesItem.keySeries" style="color:red">
|
||||
Key Images
|
||||
</div>
|
||||
<div v-else>#{{ seriesItem.seriesNumber }}</div>
|
||||
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1">
|
||||
<el-popover placement="right-start" trigger="click"
|
||||
popper-class="instance_frame_wrapper">
|
||||
<div class="frame_list">
|
||||
<div v-for="(instance, idx) in seriesItem.instanceInfoList"
|
||||
:key="instance.Id" class="frame_content"
|
||||
:style="{ 'margin-bottom': idx < seriesItem.instanceInfoList.length - 1 ? '5px' : '0px' }"
|
||||
@click="showMultiFrames(studyIndex, seriesItem, index, instance)">
|
||||
<div>
|
||||
<div>{{ instance.InstanceNumber }}</div>
|
||||
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1}
|
||||
frame`
|
||||
}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<i slot="reference" class="el-icon-connection"
|
||||
style="font-size: 15px;cursor: pointer;" />
|
||||
</el-popover>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<i slot="reference" class="el-icon-connection"
|
||||
style="font-size: 15px;cursor: pointer;" />
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-show="seriesItem.instanceCount" style="padding: 1px;">
|
||||
{{ seriesItem.modality }}: {{ seriesItem.instanceCount }} image
|
||||
</div>
|
||||
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
|
||||
T: {{ seriesItem.sliceThickness }}
|
||||
</div>
|
||||
<el-tooltip v-show="seriesItem.description" class="item" effect="dark"
|
||||
:content="seriesItem.description" placement="bottom">
|
||||
<div v-show="seriesItem.description"
|
||||
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
|
||||
{{ seriesItem.description }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
</div>
|
||||
<div v-show="seriesItem.instanceCount" style="padding: 1px;">
|
||||
{{ seriesItem.modality }}: {{ seriesItem.instanceCount }} image
|
||||
</div>
|
||||
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
|
||||
T: {{ seriesItem.sliceThickness }}
|
||||
</div>
|
||||
<el-tooltip v-show="seriesItem.description" class="item" effect="dark"
|
||||
:content="seriesItem.description" placement="bottom">
|
||||
<div v-show="seriesItem.description"
|
||||
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
|
||||
{{ seriesItem.description }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- <el-image
|
||||
class="image-preview"
|
||||
style="height:72px;width:72px;"
|
||||
:src="seriesItem.previewImageUrl"
|
||||
fit="fill"
|
||||
/> -->
|
||||
<div
|
||||
v-if="seriesItem.prefetchInstanceCount > 0 && seriesItem.prefetchInstanceCount < seriesItem.instanceCount * 100">
|
||||
<el-progress
|
||||
:percentage="parseInt((seriesItem.prefetchInstanceCount / seriesItem.instanceCount).toFixed(2))" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <el-image
|
||||
class="image-preview"
|
||||
style="height:72px;width:72px;"
|
||||
:src="seriesItem.previewImageUrl"
|
||||
fit="fill"
|
||||
/> -->
|
||||
<div
|
||||
v-if="seriesItem.prefetchInstanceCount > 0 && seriesItem.prefetchInstanceCount < seriesItem.instanceCount * 100">
|
||||
<el-progress
|
||||
:percentage="parseInt((seriesItem.prefetchInstanceCount / seriesItem.instanceCount).toFixed(2))" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</el-collapse>
|
||||
|
||||
</div>
|
||||
|
|
@ -1012,7 +1009,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.imageBox {
|
||||
.viewerContainer .imageBox {
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
// console.log(type, No);
|
||||
if (type !== "Chrome" && type !== "Edge") {
|
||||
this.tip = this.$t("browser:tip:changeBorwser");
|
||||
this.getSystemInfo()
|
||||
// this.getSystemInfo()
|
||||
return (this.visible = true);
|
||||
}
|
||||
let res = await this.getInfo();
|
||||
|
|
@ -53,10 +53,10 @@ export default {
|
|||
this.tip += "、";
|
||||
}
|
||||
});
|
||||
this.getSystemInfo()
|
||||
// this.getSystemInfo()
|
||||
return (this.visible = true);
|
||||
}
|
||||
this.getSystemInfo()
|
||||
// this.getSystemInfo()
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ export default {
|
|||
padding: 5px 20px;
|
||||
margin-top: 5px;
|
||||
background: #fff;
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
padding-bottom: 20px;
|
||||
|
||||
}
|
||||
|
|
@ -292,10 +292,10 @@ export default {
|
|||
}
|
||||
}
|
||||
.form-label-width{
|
||||
::v-deep.el-form-item__label{
|
||||
::v-deep .el-form-item__label{
|
||||
width: 140px;
|
||||
}
|
||||
::v-deep.el-form-item__content{
|
||||
::v-deep .el-form-item__content{
|
||||
margin-left: 140px;
|
||||
}
|
||||
}
|
||||
|
|
@ -304,7 +304,7 @@ export default {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 0px;
|
||||
padding-top: 5px;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
.equipment_form_content{
|
||||
padding: 0 10px;
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 0px;
|
||||
padding: 5px 0 20px 0;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
.participants_form_content{
|
||||
padding: 0 10px;
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 0px;
|
||||
padding: 5px 0 20px 0;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
.participants_form_content{
|
||||
padding: 0 10px;
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 0px;
|
||||
padding: 5px 0 20px 0;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ export default {
|
|||
padding: 5px 20px;
|
||||
margin-top: 5px;
|
||||
background: #fff;
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -398,7 +398,7 @@ export default {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
::v-deep.el-form-item {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 0px;
|
||||
padding-top: 5px;
|
||||
border-bottom: 1px solid #f5f7fa;
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ export default {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
::v-deep.el-card__body {
|
||||
::v-deep .el-card__body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
|
@ -365,13 +365,13 @@ export default {
|
|||
// padding: 10px 20px 20px 20px;
|
||||
// }
|
||||
|
||||
::v-deep.full-dialog-container {
|
||||
::v-deep .full-dialog-container {
|
||||
.el-dialog__body {
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.dialog-container {
|
||||
::v-deep .dialog-container {
|
||||
// margin-top: 50px !important;
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export default {
|
|||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
::v-deep.el-card__body {
|
||||
::v-deep .el-card__body {
|
||||
padding: 0px;
|
||||
}
|
||||
.trials-tab{
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/mixin.scss";
|
||||
@import "~@/styles/variables.module.scss";
|
||||
// @import "~@/styles/mixin.scss";
|
||||
// @import "~@/styles/variables.module.scss";
|
||||
@use "@/styles/mixin.scss" as *;
|
||||
@use "@/styles/variables.module.scss" as *;
|
||||
|
||||
.trials-wrapper {
|
||||
@include clearfix;
|
||||
|
|
|
|||
|
|
@ -495,17 +495,18 @@
|
|||
<h2 v-else style="color:#ddd">
|
||||
Developing...
|
||||
</h2>
|
||||
<div v-if="iseCRFShowInDicomReading && currentReadingTaskState < 2" v-show="listShow" class="form-footer">
|
||||
<el-button type="primary" size="small" @click="skipTask">
|
||||
<!-- 跳过 -->
|
||||
{{ $t('trials:readingReport:button:skip') }}
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="submit">
|
||||
<!-- 提交 -->
|
||||
{{ $t('common:button:submit') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="iseCRFShowInDicomReading && currentReadingTaskState < 2" v-show="listShow" class="form-footer">
|
||||
<el-button type="primary" size="small" @click="skipTask">
|
||||
<!-- 跳过 -->
|
||||
{{ $t('trials:readingReport:button:skip') }}
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="submit">
|
||||
<!-- 提交 -->
|
||||
{{ $t('common:button:submit') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
|
||||
|
||||
|
|
@ -704,6 +705,7 @@ import { getAutoCutNextTask } from '@/api/user'
|
|||
import const_ from '@/const/sign-code'
|
||||
import { changeURLStatic } from '@/utils/history.js'
|
||||
import SystemInfo from "@/utils/systemInfo";
|
||||
import md5 from 'js-md5'
|
||||
export default {
|
||||
name: 'DicomViewer',
|
||||
components: {
|
||||
|
|
@ -987,6 +989,7 @@ export default {
|
|||
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.iseCRFShowInDicomReading, this.currentReadingTaskState, this.listShow)
|
||||
this.getHotKeys()
|
||||
this.getWwcTpl()
|
||||
this.getTrialCriterion()
|
||||
|
|
@ -1138,6 +1141,7 @@ export default {
|
|||
let windowHeight = document.documentElement.clientHeight;
|
||||
this.AspectRatio = windowWidth / windowHeight
|
||||
};
|
||||
this.getSystemInfoReading()
|
||||
},
|
||||
beforeDestroy() {
|
||||
DicomEvent.$off('updateImage')
|
||||
|
|
@ -2501,6 +2505,30 @@ export default {
|
|||
|
||||
})
|
||||
},
|
||||
async getSystemInfoReading() {
|
||||
return new Promise(async resolve => {
|
||||
let whitelisting = localStorage.getItem('whitelisting') ? JSON.parse(localStorage.getItem('whitelisting')) : []
|
||||
let user = md5(sessionStorage.getItem('identityUserId'))
|
||||
let r = whitelisting.some(item => item === user)
|
||||
if (r) return resolve(true)
|
||||
const systemInfo = new SystemInfo();
|
||||
const allInfo = systemInfo.getAllInfo();
|
||||
let deviceMemory = allInfo.hardware.deviceMemory; // 设备内存
|
||||
let { width, height } = allInfo.screen; // 分辨率
|
||||
// let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡
|
||||
// let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存
|
||||
// parseFloat(deviceMemory) < 16 ||
|
||||
if (width < 1920 || height < 1080) {
|
||||
let res = await this.$confirm(this.$t('browser:tip:Configuration'))
|
||||
whitelisting.push(user)
|
||||
localStorage.setItem('whitelisting', JSON.stringify(whitelisting))
|
||||
resolve(res)
|
||||
} else {
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -2786,7 +2814,7 @@ export default {
|
|||
|
||||
.form-footer {
|
||||
background: #000;
|
||||
padding: 10px 0;
|
||||
padding: 5px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
@ -2900,12 +2928,12 @@ export default {
|
|||
}
|
||||
|
||||
.series-table {
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
}
|
||||
|
|
@ -2916,14 +2944,14 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -2934,7 +2962,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,23 +319,23 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.series-table{
|
||||
::v-deep.el-table{
|
||||
::v-deep .el-table{
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
::v-deep.el-table td.el-table__cell, .el-table th.el-table__cell.is-leaf{
|
||||
::v-deep .el-table td.el-table__cell, .el-table th.el-table__cell.is-leaf{
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
}
|
||||
.el-table--border::after, .el-table--group::after, .el-table::before{
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
::v-deep.el-table__header-wrapper{
|
||||
::v-deep .el-table__header-wrapper{
|
||||
th{
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
}
|
||||
::v-deep.el-table__body-wrapper{
|
||||
::v-deep .el-table__body-wrapper{
|
||||
tr{
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -344,7 +344,7 @@ export default {
|
|||
background-color: #1e1e1e !important;
|
||||
}
|
||||
}
|
||||
::v-deep.el-table__empty-block{
|
||||
::v-deep .el-table__empty-block{
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,46 +140,8 @@
|
|||
</div>
|
||||
</el-tooltip>
|
||||
<!-- 伪彩 -->
|
||||
<el-tooltip class="item" effect="dark" :content="$t('trials:lugano:button:colormap')" placement="bottom">
|
||||
<div class="colorBar" style="display:flex;justify-content: flex-start;align-items: center;position: relative;"
|
||||
@mouseleave="isSlideMoving = false">
|
||||
<div class="tool-wrapper" style="margin-right:0px" @click.stop="showColorBarPanel($event)"
|
||||
@mouseleave="handleColorBarMouseout">
|
||||
<div>
|
||||
<!-- <canvas id="colorBarCanvas" /> -->
|
||||
<div class="dropdown">
|
||||
<div id="colorBar" class="icon" style="display: flex;align-items: center;width:266px">
|
||||
<canvas id="colorBarCanvas" />
|
||||
|
||||
</div>
|
||||
<!-- 伪彩 -->
|
||||
<div class="text">{{ $t('trials:lugano:button:colormap') }}</div>
|
||||
<div class="dropdown-content" style="width:266px">
|
||||
<ul>
|
||||
<li v-for="(colorMap, index) in colorMaps" :key="colorMap"
|
||||
style="display: flex;align-items: center;margin-bottom:5px;padding:0 5px;justify-content: space-between;"
|
||||
:class="{ activeLi: rgbPresetName === colorMap }" @click="setColorMap(colorMap)">
|
||||
<canvas :id="`colorBarCanvas${index}`" />
|
||||
<span style="margin-left:5px;font-size: 10px;">{{ colorMap }}</span>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left:-1px;border: 1px solid #424242;">
|
||||
<el-input v-model="range" size="mini" style="width:120px" maxlength="3"
|
||||
oninput="if(value){value=value.replace(/[^\d]/g,'')} if(value<=0){value=''}" @change="upperRangeChange">
|
||||
<template slot="append">g/ml</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div id="slider" style="position: absolute;left: 6px;top:5px;cursor: pointer;">
|
||||
<div id="sliderBox" class="slider" style="height: 17px;width: 10px;background-color: #909399;" />
|
||||
<div id="slider-position" style="color:#ddd;font-size: 12px;">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<colorMap v-show="isFusion" ref="colorMap" :unit="fusionOverlayModality === 'NM' ? 'counts' : 'g/ml'"
|
||||
:modality="fusionOverlayModality" @setColorMap="setColorMap" @voiChange="voiChange" />
|
||||
|
||||
<!-- 截屏 -->
|
||||
<!-- <el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:screenShot')}`" placement="bottom">
|
||||
|
|
@ -217,15 +179,14 @@
|
|||
<Viewport ref="CT_AXIAL" :index="1" :active-index="activeIndex"
|
||||
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="ctSeries"
|
||||
:rendering-engine-id="renderingEngineId" viewport-id="CT_AXIAL" :volume="ctVolume"
|
||||
:measure-datas="measureDatas" :style="1 === activeIndex ? viewportStyle : {}"
|
||||
@upperRangeChange="upperRangeChange" />
|
||||
:measure-datas="measureDatas" :style="1 === activeIndex ? viewportStyle : {}" />
|
||||
<Viewport ref="PT_AXIAL" :index="2" :active-index="activeIndex"
|
||||
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries"
|
||||
:rendering-engine-id="renderingEngineId" viewport-id="PT_AXIAL" :volume="ptVolume"
|
||||
:measure-datas="measureDatas" :style="2 === activeIndex ? viewportStyle : {}"
|
||||
@upperRangeChange="upperRangeChange" />
|
||||
<Viewport ref="FUSION_AXIAL" :index="3" :active-index="activeIndex"
|
||||
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries"
|
||||
:is-reading-show-subject-info="isReadingShowSubjectInfo" :series-info="petSeries" :ct-series-info="ctSeries"
|
||||
:rendering-engine-id="renderingEngineId" viewport-id="FUSION_AXIAL" :volume="ptVolume"
|
||||
:measure-datas="measureDatas" :rgb-preset-name="rgbPresetName" :style="3 === activeIndex ? viewportStyle : {}"
|
||||
@upperRangeChange="upperRangeChange" />
|
||||
|
|
@ -306,6 +267,7 @@ import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunc
|
|||
import { mat4, vec3 } from 'gl-matrix'
|
||||
import html2canvas from 'html2canvas'
|
||||
import readingChart from '@/components/readingChart'
|
||||
import colorMap from '@/views/trials/trials-panel/reading/dicoms3D/components/colorMap.vue'
|
||||
// import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'
|
||||
// import vtkMath from '@kitware/vtk.js/Common/Core/Math'
|
||||
// import CircleROITool from './tools/CircleROITool'
|
||||
|
|
@ -395,7 +357,8 @@ export default {
|
|||
TableQuestions,
|
||||
CustomWwwcForm,
|
||||
FusionForm,
|
||||
readingChart
|
||||
readingChart,
|
||||
colorMap
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -441,6 +404,8 @@ export default {
|
|||
upper: 6,
|
||||
isSlideMoving: false,
|
||||
viewportStyle: {},
|
||||
isFusion: true,
|
||||
fusionOverlayModality: 'PT',
|
||||
defaultWwwc: [
|
||||
{ label: this.$t('trials:reading:button:wwwcDefault'), val: -1, ww: null, wc: null }, // 默认值
|
||||
{ label: this.$t('trials:reading:button:wwwcCustom'), val: 0, ww: null, wc: null }, // 自定义
|
||||
|
|
@ -458,7 +423,9 @@ export default {
|
|||
activeCanvasWW: null,
|
||||
activeCanvasWC: null,
|
||||
fusion: { visible: false }, // 历史记录融合调窗
|
||||
screenshotWindow: null
|
||||
screenshotWindow: null,
|
||||
hasVoiChanged: false,
|
||||
lastUpper: null
|
||||
// initFirstAnnotation:false
|
||||
}
|
||||
},
|
||||
|
|
@ -512,13 +479,13 @@ export default {
|
|||
})
|
||||
this.colorMaps = getColormapNames()
|
||||
this.colorMaps.unshift('hsv')
|
||||
this.$nextTick(() => {
|
||||
this.renderColorMaps()
|
||||
this.handleElementsClick()
|
||||
this.initPage()
|
||||
this.upperRangeChange(this.range)
|
||||
this.initSlider()
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
// this.renderColorMaps()
|
||||
this.handleElementsClick()
|
||||
this.initPage()
|
||||
// this.upperRangeChange(this.range)
|
||||
// this.initSlider()
|
||||
})
|
||||
|
||||
FusionEvent.$on('getAnnotations', () => {
|
||||
console.log('getAnnotations')
|
||||
|
|
@ -744,6 +711,7 @@ export default {
|
|||
return new Promise(resolve => {
|
||||
getDicomSeriesInfo({ seriesId }).then(res => {
|
||||
var series = res.Result
|
||||
this.fusionOverlayModality = series.Modality
|
||||
var imageIds = []
|
||||
var instanceList = []
|
||||
series.InstanceInfoList.forEach(instance => {
|
||||
|
|
@ -1057,7 +1025,7 @@ export default {
|
|||
// volume to use for the WindowLevelTool for the fusion viewports
|
||||
|
||||
fusionToolGroup.addTool(WindowLevelTool.toolName, {
|
||||
volumeId: ptVolumeId
|
||||
volumeId: ptVolumeId2
|
||||
});
|
||||
|
||||
[ctToolGroup, ptToolGroup, fusionToolGroup].forEach((toolGroup) => {
|
||||
|
|
@ -1627,148 +1595,141 @@ export default {
|
|||
var slider = document.getElementById('slider')
|
||||
var sliderBox = document.getElementById('sliderBox')
|
||||
var container = document.getElementById('colorBarCanvas')
|
||||
slider.addEventListener('mousedown', () => {
|
||||
|
||||
if (!slider || !sliderBox || !container) return
|
||||
|
||||
slider.addEventListener('mousedown', (e) => {
|
||||
this.isSlideMoving = true
|
||||
e.stopPropagation()
|
||||
// document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
|
||||
// document.ondragstart = function() { return false }
|
||||
})
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (this.isSlideMoving) {
|
||||
var containerWidth = container.clientWidth
|
||||
var colorBarContainer = document.getElementById('colorBar')
|
||||
if (!colorBarContainer) return
|
||||
|
||||
var containerWidth = colorBarContainer.clientWidth
|
||||
var sliderWidth = sliderBox.clientWidth
|
||||
var maxLeft = containerWidth - sliderWidth
|
||||
var left = e.clientX - container.getBoundingClientRect().left
|
||||
var position = null
|
||||
position = left
|
||||
|
||||
// 获取相对于整个父容器的外边距位置
|
||||
var rect = colorBarContainer.getBoundingClientRect()
|
||||
var left = e.clientX - rect.left - (sliderWidth / 2)
|
||||
|
||||
if (left < 0) {
|
||||
left = 6
|
||||
position = 0
|
||||
left = 0
|
||||
} else if (left > maxLeft) {
|
||||
left = maxLeft + 6
|
||||
position = maxLeft
|
||||
left = maxLeft
|
||||
}
|
||||
|
||||
slider.style.left = left + 'px'
|
||||
var positionValue = document.getElementById('slider-position')
|
||||
var upper = this.range
|
||||
position = parseInt((position / maxLeft) * upper)
|
||||
positionValue.textContent = position
|
||||
var position = parseInt((left / maxLeft) * upper)
|
||||
|
||||
if (positionValue) {
|
||||
positionValue.textContent = position
|
||||
}
|
||||
this.upper = position
|
||||
this.voiChange(position)
|
||||
|
||||
if (this.sliderTimeout) {
|
||||
cancelAnimationFrame(this.sliderTimeout)
|
||||
}
|
||||
this.sliderTimeout = requestAnimationFrame(() => {
|
||||
this.voiChange(position)
|
||||
})
|
||||
}
|
||||
})
|
||||
document.addEventListener('mouseup', () => {
|
||||
this.isSlideMoving = false
|
||||
document.addEventListener('mouseup', (e) => {
|
||||
if (this.isSlideMoving) {
|
||||
this.isSlideMoving = false
|
||||
if (this.sliderTimeout) {
|
||||
cancelAnimationFrame(this.sliderTimeout)
|
||||
}
|
||||
this.voiChange(this.upper)
|
||||
}
|
||||
// document.onselectstart = null
|
||||
// document.ondragstart = null
|
||||
})
|
||||
},
|
||||
upperRangeChange(v) {
|
||||
if (v === 0 || v < this.upper) {
|
||||
return
|
||||
}
|
||||
var sliderBox = document.getElementById('sliderBox')
|
||||
var container = document.getElementById('colorBarCanvas')
|
||||
var containerWidth = container.clientWidth
|
||||
var sliderWidth = sliderBox.clientWidth
|
||||
var maxLeft = containerWidth - sliderWidth
|
||||
var left = (this.upper / this.range) * maxLeft
|
||||
if (left < 0) {
|
||||
left = 6
|
||||
} else if (left >= maxLeft) {
|
||||
left = maxLeft + 6
|
||||
}
|
||||
var slider = document.getElementById('slider')
|
||||
slider.style.left = left + 'px'
|
||||
var positionValue = document.getElementById('slider-position')
|
||||
positionValue.textContent = this.upper
|
||||
},
|
||||
renderColorMaps() {
|
||||
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
|
||||
this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName)
|
||||
this.colorMaps.forEach((e, index) => {
|
||||
this.createColorBar(e, `colorBarCanvas${index}`, 180, 15)
|
||||
})
|
||||
},
|
||||
getVOIRange(viewportId, volumeId) {
|
||||
const defaultImageRange = { lower: -1000, upper: 1000 }
|
||||
const viewport = (
|
||||
renderingEngine.getViewport(viewportId)
|
||||
)
|
||||
const volumeActor = volumeId
|
||||
? viewport.getActor(volumeId)
|
||||
: viewport.getDefaultActor()
|
||||
|
||||
if (!volumeActor || !csUtils.isImageActor(volumeActor)) {
|
||||
return defaultImageRange
|
||||
}
|
||||
|
||||
const voiRange = (volumeActor.actor)
|
||||
.getProperty()
|
||||
.getRGBTransferFunction(0)
|
||||
.getRange()
|
||||
|
||||
return voiRange[0] === 0 && voiRange[1] === 0
|
||||
? defaultImageRange
|
||||
: { lower: voiRange[0], upper: voiRange[1] }
|
||||
},
|
||||
voiChange(v) {
|
||||
// console.log(this.lastUpper, this.hasVoiChanged, v)
|
||||
if (this.lastUpper === v && this.hasVoiChanged) return;
|
||||
this.lastUpper = v;
|
||||
this.hasVoiChanged = true;
|
||||
let viewportIds = ['FUSION_AXIAL', 'PT_AXIAL', 'PET_MIP_CORONAL']
|
||||
viewportIds.map(viewportId => {
|
||||
// const volumeId = viewportId === 'viewportId' ? ptVolumeId : ctVolumeId
|
||||
const volumeId = ptVolumeId
|
||||
const volumeId = viewportId === 'FUSION_AXIAL' ? ptVolumeId2 : ptVolumeId
|
||||
const voiRange = { lower: 0, upper: v }
|
||||
const viewport = (
|
||||
renderingEngine.getViewport(viewportId)
|
||||
)
|
||||
if (!viewport) return
|
||||
// const volumeId = viewport.getVolumeId()
|
||||
// const viewportsContainingVolumeUID = csUtils.getViewportsWithVolumeId(
|
||||
// volumeId,
|
||||
// viewport.renderingEngineId
|
||||
// )
|
||||
|
||||
|
||||
viewport.setProperties({ voiRange }, volumeId)
|
||||
viewport.render()
|
||||
this.$refs[viewport.id].setWwWc()
|
||||
// viewportsContainingVolumeUID.forEach((vp) => {
|
||||
// vp.render()
|
||||
// this.$refs[vp.id].setWwWc()
|
||||
// console.log(vp.id)
|
||||
// })
|
||||
this.$refs[viewport.id] && this.$refs[viewport.id].setWwWc()
|
||||
})
|
||||
|
||||
// 反向同步到 colorMap 组件,更新滑块
|
||||
if (this.$refs.colorMap) {
|
||||
this.$refs.colorMap.changeVoi(v)
|
||||
}
|
||||
},
|
||||
// async setColorMap(rgbPresetName) {
|
||||
// this.rgbPresetName = rgbPresetName
|
||||
// if (this.$refs.FUSION_AXIAL) {
|
||||
// this.$refs.FUSION_AXIAL.setPreset(this.rgbPresetName)
|
||||
// this.$refs.FUSION_AXIAL.renderColorBar(this.rgbPresetName)
|
||||
// }
|
||||
|
||||
// const viewport = getRenderingEngine(renderingEngineId)?.getViewport('FUSION_AXIAL')
|
||||
// if (!viewport) return
|
||||
|
||||
// const actorEntry = viewport.getActors()?.find(e => String(e.uid || '').includes('PT_VOLUME_ID2'))
|
||||
// if (!actorEntry?.actor) return
|
||||
|
||||
// const preset = rgbPresetName === 'hsv' ? vtkColorMaps.getPresetByName(rgbPresetName) : getColormap(rgbPresetName)
|
||||
// const cfun = vtkColorTransferFunction.newInstance()
|
||||
// const rgbPoints = preset?.RGBPoints
|
||||
// if (Array.isArray(rgbPoints)) {
|
||||
// for (let i = 0; i < rgbPoints.length; i += 4) {
|
||||
// cfun.addRGBPoint(rgbPoints[i], rgbPoints[i + 1], rgbPoints[i + 2], rgbPoints[i + 3])
|
||||
// }
|
||||
// }
|
||||
// cfun.setMappingRange(0, 5)
|
||||
// actorEntry.actor.getProperty().setRGBTransferFunction(0, cfun)
|
||||
|
||||
// const ofun = vtkPiecewiseFunction.newInstance()
|
||||
// ofun.addPoint(0, 0.0)
|
||||
// ofun.addPoint(0.1, 0.9)
|
||||
// ofun.addPoint(5, 1.0)
|
||||
// actorEntry.actor.getProperty().setScalarOpacity(0, ofun)
|
||||
|
||||
// viewport.render()
|
||||
// },
|
||||
async setColorMap(rgbPresetName) {
|
||||
this.rgbPresetName = rgbPresetName
|
||||
let viewports = ['FUSION_AXIAL', 'PT_AXIAL', 'PET_MIP_CORONAL']
|
||||
let viewports = ['FUSION_AXIAL']
|
||||
viewports.map(v => {
|
||||
this.$refs[v].setPreset(this.rgbPresetName)
|
||||
this.$refs[v].renderColorBar(this.rgbPresetName)
|
||||
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
|
||||
const viewport = (
|
||||
renderingEngine.getViewport(v)
|
||||
)
|
||||
|
||||
viewport.setProperties({ colormap: { name: rgbPresetName } }, ptVolumeId)
|
||||
viewport.render()
|
||||
this.$refs[v] && this.$refs[v].setPreset(this.rgbPresetName)
|
||||
this.$refs[v] && this.$refs[v].setColorMap(rgbPresetName)
|
||||
|
||||
})
|
||||
// this.$refs['FUSION_AXIAL'].setPreset(this.rgbPresetName)
|
||||
// this.$refs['FUSION_AXIAL'].renderColorBar(this.rgbPresetName)
|
||||
// this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
|
||||
// const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
},
|
||||
upperRangeChange(v) {
|
||||
if (this.upper === v) return
|
||||
this.upper = v;
|
||||
|
||||
// const viewport = (
|
||||
// renderingEngine.getViewport(viewportIds.FUSION.AXIAL)
|
||||
// )
|
||||
|
||||
// viewport.setProperties({ colormap: { name: rgbPresetName } }, ptVolumeId)
|
||||
// // viewport.setProperties({ colormap: { name: rgbPresetName }}, ctVolumeId)
|
||||
// viewport.render()
|
||||
// document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
|
||||
// document.ondragstart = function() { return false }
|
||||
if (v === 0) {
|
||||
return
|
||||
}
|
||||
this.voiChange(v)
|
||||
// 反向同步到 colorMap 组件,更新滑块
|
||||
if (this.$refs.colorMap) {
|
||||
this.$refs.colorMap.changeVoi(v)
|
||||
}
|
||||
},
|
||||
setPetColorMap(volumeInfo) {
|
||||
const { volumeActor } = volumeInfo
|
||||
|
|
@ -1979,17 +1940,24 @@ export default {
|
|||
// document.onselectstart = function() { return false }// 解决拖动会选中文字的问题
|
||||
// document.ondragstart = function() { return false }
|
||||
},
|
||||
setWwwc(v) {
|
||||
// this.changeMapperRange(v.wc, v.ww)
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
let viewportId = this.activeIndex === 1 ? 'CT_AXIAL' : this.activeIndex === 2 ? 'PT_AXIAL' : this.activeIndex === 3 ? 'FUSION_AXIAL' : 'PET_MIP_CORONAL'
|
||||
const viewport = renderingEngine.getViewport(viewportId)
|
||||
const lower = v.wc - v.ww / 2
|
||||
const upper = v.wc + v.ww / 2 - 1
|
||||
viewport.setProperties({ voiRange: { upper: upper, lower: lower } })
|
||||
viewport.render()
|
||||
this.customWwc.visible = false
|
||||
},
|
||||
// setWwwc(v) {
|
||||
// // this.changeMapperRange(v.wc, v.ww)
|
||||
// const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
// let viewportId = this.activeIndex === 1 ? 'CT_AXIAL' : this.activeIndex === 2 ? 'PT_AXIAL' : this.activeIndex === 3 ? 'FUSION_AXIAL' : 'PET_MIP_CORONAL'
|
||||
// const viewport = renderingEngine.getViewport(viewportId)
|
||||
// const lower = v.wc - v.ww / 2
|
||||
// const upper = v.wc + v.ww / 2 - 1
|
||||
// viewport.setProperties({ voiRange: { upper: upper, lower: lower } })
|
||||
// viewport.render()
|
||||
// if (viewportId !== 'CT_AXIAL') {
|
||||
// const nextUpper = Math.round(upper)
|
||||
// this.upper = nextUpper
|
||||
// if (this.$refs.colorMap) {
|
||||
// this.$refs.colorMap.changeVoi(nextUpper)
|
||||
// }
|
||||
// }
|
||||
// this.customWwc.visible = false
|
||||
// },
|
||||
changeMapperRange(wc, ww) {
|
||||
var lower = wc - ww / 2.0
|
||||
var upper = wc + ww / 2.0
|
||||
|
|
@ -2262,7 +2230,7 @@ export default {
|
|||
background-color: #000;
|
||||
padding: 5px 2px;
|
||||
|
||||
::v-deep.el-dialog {
|
||||
::v-deep .el-dialog {
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #ddd;
|
||||
color: #ddd;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
</div>
|
||||
<div v-if="index !== 4">Image: #{{ `${seriesInfo.imageIdIndex + 1} / ${seriesInfo.imageMaxLength}` }}</div>
|
||||
<div v-if="index === 1 || index === 2">{{ seriesInfo.modality }}</div>
|
||||
<div v-if="index === 3">{{ ctSeriesInfo ? ctSeriesInfo.modality + ' / ' + seriesInfo.modality : seriesInfo.modality }}</div>
|
||||
<div v-if="index === 4">MIP</div>
|
||||
</div>
|
||||
<!-- 描述信息 -->
|
||||
<div
|
||||
|
|
@ -135,6 +137,12 @@ export default {
|
|||
return {}
|
||||
}
|
||||
},
|
||||
ctSeriesInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
},
|
||||
renderingEngineId: {
|
||||
type: String,
|
||||
required: true
|
||||
|
|
@ -200,7 +208,6 @@ export default {
|
|||
mounted() {
|
||||
const digitPlaces = parseInt(this.$route.query.digitPlaces)
|
||||
this.digitPlaces = digitPlaces === -1 ? 2 : digitPlaces
|
||||
console.log(toolsUtilities)
|
||||
this.subjectCode = this.$route.query.subjectCode
|
||||
var element = document.getElementById(`viewport${this.index}`)
|
||||
element.addEventListener(VOLUME_NEW_IMAGE, this.handleVolumeNewImage)
|
||||
|
|
@ -328,7 +335,7 @@ export default {
|
|||
let properties = viewport.getProperties()
|
||||
if (this.index === 3) {
|
||||
// const volumeId = viewport.getVolumeId()
|
||||
const volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID`
|
||||
const volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID2`
|
||||
properties = viewport.getProperties(volumeId)
|
||||
}
|
||||
if (properties && properties.voiRange) {
|
||||
|
|
@ -338,6 +345,10 @@ export default {
|
|||
upper
|
||||
)
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
// 当为PT或融合视口或MIP视口时,触发外部联动 (排除纯CT视口)
|
||||
if (this.index !== 1) {
|
||||
this.$emit('upperRangeChange', Math.round(upper))
|
||||
}
|
||||
}
|
||||
},
|
||||
handleMouseMove(e) {
|
||||
|
|
@ -567,10 +578,17 @@ export default {
|
|||
ctx.fillStyle = gradient
|
||||
ctx.fillRect(0, 0, rectWidth, rectHeight)
|
||||
},
|
||||
setColorMap(presetName) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
let volumeId = `cornerstoneStreamingImageVolume:PT_VOLUME_ID`
|
||||
viewport.setProperties({ colormap: { name: presetName } }, volumeId)
|
||||
viewport.render()
|
||||
},
|
||||
setWwWc() {
|
||||
let properties = viewport.getProperties()
|
||||
if (this.index === 3) {
|
||||
properties = viewport.getProperties(`cornerstoneStreamingImageVolume:PT_VOLUME_ID`)
|
||||
properties = viewport.getProperties(`cornerstoneStreamingImageVolume:PT_VOLUME_ID2`)
|
||||
}
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
|
|
@ -582,7 +600,10 @@ export default {
|
|||
upper
|
||||
)
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
this.$emit('upperRangeChange', Math.round(windowWidth))
|
||||
// 当为PT或融合视口或MIP视口时,触发外部联动 (排除纯CT视口)
|
||||
if (this.index !== 1) {
|
||||
this.$emit('upperRangeChange', Math.round(upper))
|
||||
}
|
||||
}
|
||||
},
|
||||
async getScreenshots() {
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
<template>
|
||||
<div v-loading="loading" class="ecrf-wrapper">
|
||||
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
|
||||
评估说明
|
||||
</h4>
|
||||
<el-form
|
||||
ref="questions"
|
||||
size="small"
|
||||
:model="questionForm"
|
||||
>
|
||||
<el-form-item
|
||||
label="访视点备注"
|
||||
prop="note"
|
||||
:rules="[
|
||||
{ required: true,
|
||||
message:$t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
|
||||
]"
|
||||
>
|
||||
<el-input
|
||||
v-model="questionForm.note"
|
||||
:disabled="readingTaskState >= 2"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 4}"
|
||||
maxlength="500"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="readingTaskState < 2">
|
||||
<div style="text-align:right">
|
||||
<el-button size="mini" @click="handleSave">{{ $t('common:button:save') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'ECRF',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
questionForm: {
|
||||
note: ''
|
||||
},
|
||||
readingTaskState: 1,
|
||||
isBaseLineTask: false,
|
||||
visitTaskId: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.trialId = this.$route.query.trialId
|
||||
this.visitTaskId = this.$route.query.visitTaskId
|
||||
},
|
||||
beforeDestroy() {
|
||||
},
|
||||
methods: {
|
||||
handleSave() {
|
||||
this.$refs['questions'].validate((valid) => {
|
||||
if (!valid) return
|
||||
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ecrf-wrapper{
|
||||
|
||||
::v-deep .el-form-item__label{
|
||||
color: #c3c3c3;
|
||||
text-align: left;
|
||||
}
|
||||
::v-deep .el-input__inner{
|
||||
background-color: transparent;
|
||||
color: #ddd;
|
||||
border: 1px solid #5e5e5e;
|
||||
}
|
||||
::v-deep .el-textarea__inner{
|
||||
background-color: transparent;
|
||||
color: #ddd;
|
||||
border: 1px solid #5e5e5e;
|
||||
}
|
||||
::v-deep .el-form-item{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
::v-deep .el-form-item__content{
|
||||
flex: 1;
|
||||
}
|
||||
::v-deep .el-button--mini, .el-button--mini.is-round {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
.el-form-item__content
|
||||
.el-select{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
<template>
|
||||
<div v-loading="loading" class="ecrf-wrapper">
|
||||
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
|
||||
影像质量
|
||||
</h4>
|
||||
<el-form
|
||||
ref="questions"
|
||||
size="small"
|
||||
:model="questionForm"
|
||||
>
|
||||
<el-form-item
|
||||
label="影像质量"
|
||||
prop="imageQuality"
|
||||
:rules="[
|
||||
{ required: true,
|
||||
message:$t('common:ruleMessage:select'), trigger: ['blur', 'change']},
|
||||
]"
|
||||
>
|
||||
<el-radio-group v-model="questionForm.imageQuality">
|
||||
<el-radio
|
||||
v-for="item of $d.ImageQualityEvaluation"
|
||||
:key="item.id"
|
||||
:label="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="questionForm.imageQuality === 2"
|
||||
label="影像质量问题"
|
||||
prop="imageQualityIssues"
|
||||
:rules="[
|
||||
{ required: true,
|
||||
message:$t('common:ruleMessage:select'), trigger: ['blur', 'change']},
|
||||
]"
|
||||
>
|
||||
<el-select
|
||||
v-model="questionForm.imageQualityIssues"
|
||||
:disabled="readingTaskState >= 2"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item of $d.ImageQualityIssues"
|
||||
:key="item.id"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="questionForm.imageQualityIssues === 5"
|
||||
label="影像质量备注"
|
||||
prop="note"
|
||||
:rules="[
|
||||
{ required: true,
|
||||
message:$t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
|
||||
]"
|
||||
>
|
||||
<el-input
|
||||
v-model="questionForm.note"
|
||||
:disabled="readingTaskState >= 2"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 4}"
|
||||
maxlength="500"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="readingTaskState < 2">
|
||||
<div style="text-align:right">
|
||||
<el-button size="mini" @click="handleSave">{{ $t('common:button:save') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'ECRF',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
questionForm: {
|
||||
imageQuality: null,
|
||||
imageQualityIssues: null,
|
||||
note: ''
|
||||
},
|
||||
readingTaskState: 1,
|
||||
isBaseLineTask: false,
|
||||
visitTaskId: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.trialId = this.$route.query.trialId
|
||||
},
|
||||
beforeDestroy() {
|
||||
},
|
||||
methods: {
|
||||
handleSave() {
|
||||
this.$refs['questions'].validate((valid) => {
|
||||
if (!valid) return
|
||||
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ecrf-wrapper{
|
||||
|
||||
::v-deep .el-form-item__label{
|
||||
color: #c3c3c3;
|
||||
text-align: left;
|
||||
}
|
||||
::v-deep .el-input__inner{
|
||||
background-color: transparent;
|
||||
color: #ddd;
|
||||
border: 1px solid #5e5e5e;
|
||||
}
|
||||
::v-deep .el-textarea__inner{
|
||||
background-color: transparent;
|
||||
color: #ddd;
|
||||
border: 1px solid #5e5e5e;
|
||||
}
|
||||
::v-deep .el-form-item{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
::v-deep .el-form-item__content{
|
||||
flex: 1;
|
||||
}
|
||||
::v-deep .el-button--mini, .el-button--mini.is-round {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
.el-form-item__content
|
||||
.el-select{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
|
@ -1,354 +0,0 @@
|
|||
<template>
|
||||
<div class="measurement-wrapper">
|
||||
|
||||
<div class="container">
|
||||
<h4 style="color: #ddd;padding: 5px 0px;margin: 0;">
|
||||
核医学评估
|
||||
</h4>
|
||||
<div class="lesion_list">
|
||||
<div class="flex-row" style="margin:3px 0;">
|
||||
<div class="title">盆腔淋巴结</div>
|
||||
</div>
|
||||
<el-table
|
||||
:data="questions"
|
||||
style="width: 100%"
|
||||
row-key="rowIndex"
|
||||
:expand-row-keys="expands"
|
||||
@expand-change="expandSelect"
|
||||
size="mini"
|
||||
>
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-form :ref="props.row.rowIndex" size="small" :model="props.row" label-width="80px" style="padding-right: 10px">
|
||||
<el-form-item label="部位">
|
||||
<el-input
|
||||
v-model="props.row.part"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="转移灶"
|
||||
prop="distraction"
|
||||
:rules="[
|
||||
{ required: true,
|
||||
message:$t('common:ruleMessage:select'), trigger: ['blur', 'change']},
|
||||
]"
|
||||
>
|
||||
<el-select v-model="props.row.distraction" style="width:100%;">
|
||||
<el-option label="阴性" :value="1"></el-option>
|
||||
<el-option label="阳性" :value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="SUVmax">
|
||||
<el-input
|
||||
v-model="props.row.suvMax"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item >
|
||||
<div style="text-align:right">
|
||||
<el-button v-if="props.row.annotation" size="mini" @click="clearAnnotation(props.row)">{{ $t('trials:reading:button:removeMark') }}</el-button>
|
||||
<el-button size="mini" @click="handleSave(props.row)">{{ $t('common:button:save') }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="部位"
|
||||
prop="part">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="转移灶"
|
||||
prop="distraction">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.distraction === 1 ? '阴性' : scope.row.distraction === 2 ? '阳性' : ''}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="SUVmax"
|
||||
prop="suvMax">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import FusionEvent from './../FusionEvent'
|
||||
export default {
|
||||
name: 'TableQuestionList',
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: window.innerHeight - 140,
|
||||
questions: [
|
||||
{
|
||||
rowIndex: '001',
|
||||
part: '髂内',
|
||||
distraction: '',
|
||||
suvMax: null,
|
||||
saveTypeEnum: 1,
|
||||
annotation: null
|
||||
},
|
||||
{
|
||||
rowIndex: '002',
|
||||
part: '髂外',
|
||||
distraction: '',
|
||||
suvMax: null,
|
||||
saveTypeEnum: 1,
|
||||
annotation: null
|
||||
},
|
||||
{
|
||||
rowIndex: '003',
|
||||
part: '髂总',
|
||||
distraction: '',
|
||||
suvMax: null,
|
||||
saveTypeEnum: 1,
|
||||
annotation: null
|
||||
},
|
||||
{
|
||||
rowIndex: '004',
|
||||
part: '闭孔',
|
||||
distraction: '',
|
||||
suvMax: null,
|
||||
saveTypeEnum: 1,
|
||||
annotation: null
|
||||
}
|
||||
],
|
||||
visitTaskId: '',
|
||||
isCurrentTask: false,
|
||||
loading: false,
|
||||
readingTaskState: 1,
|
||||
isBaseLineTask: false,
|
||||
taskBlindName: '',
|
||||
expands:[]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
beforeDestroy() {
|
||||
},
|
||||
methods: {
|
||||
expandSelect(row, expandedRows) {
|
||||
this.expands = []
|
||||
if (expandedRows.length > 0) {
|
||||
row ? this.expands.push(row.rowIndex) : ''
|
||||
}
|
||||
if (this.expands.length > 0 && row.annotation) {
|
||||
FusionEvent.$emit('imageLocation', { annotation: row.annotation })
|
||||
}
|
||||
},
|
||||
async clearAnnotation(row) {
|
||||
// 是否确认清除标记?
|
||||
const confirm = await this.$confirm(
|
||||
this.$t('trials:reading:warnning:msg47'),
|
||||
{
|
||||
type: 'warning',
|
||||
distinguishCancelAndClose: true
|
||||
}
|
||||
)
|
||||
if (confirm !== 'confirm') return
|
||||
let i = this.questions.findIndex(i=>i.rowIndex === row.rowIndex)
|
||||
FusionEvent.$emit('removeAnnotation', { annotation: this.questions[i].annotation })
|
||||
this.questions[i].annotation = null
|
||||
this.questions[i].suvMax = null
|
||||
},
|
||||
async handleSave(row) {
|
||||
let loading = null
|
||||
try {
|
||||
let valid = await this.$refs[row.rowIndex].validate()
|
||||
if (!valid) return
|
||||
loading = this.$loading({ fullscreen: true })
|
||||
setTimeout(() => {
|
||||
// 模拟保存成功
|
||||
loading.close()
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
|
||||
// 可以在这里添加更多的逻辑,比如更新页面上的某些元素
|
||||
}, 2000);
|
||||
} catch(e) {
|
||||
if (loading) {
|
||||
loading.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
setActiveCollapse(annotationUID) {
|
||||
for (let i = 0; i < this.questions.length; i++) {
|
||||
let obj = this.questions[i]
|
||||
if (obj.annotation && obj.annotation.data && obj.annotation.data.annotationUID === annotationUID) {
|
||||
this.expands = [this.questions[i].rowIndex]
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
isCanActiveTool(toolName) {
|
||||
if (this.expands.length > 0) {
|
||||
let isExist = this.isExistMeasureData(this.expands[0])
|
||||
return { isCanActiveTool: isExist, reason: '' }
|
||||
} else {
|
||||
return { isCanActiveTool: false, reason: '' }
|
||||
}
|
||||
},
|
||||
isExistMeasureData(rowIndex) {
|
||||
let i = this.questions.findIndex(i=>i.rowIndex === rowIndex)
|
||||
if (this.questions[i].annotation) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
// modifyMeasuredData(measureObj) {
|
||||
// if (measureObj.questionInfo) {
|
||||
// this.activeItem.activeCollapseId = measureObj.questionInfo.QuestionId
|
||||
// this.activeItem.activeRowIndex = String(measureObj.questionInfo.RowIndex)
|
||||
// this.activeName = `${this.activeItem.activeCollapseId}_${this.activeItem.activeRowIndex}`
|
||||
// const refName = `${this.activeItem.activeCollapseId}_${this.activeItem.activeRowIndex}`
|
||||
// if (this.$refs[refName]) {
|
||||
// this.$refs[refName][0].setMeasureData(measureObj.measureData)
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// 设置测量数据
|
||||
setMeasuredData(measureData) {
|
||||
if (measureData.data && measureData.data.data && measureData.data.data.remark ) {
|
||||
// 编辑
|
||||
// 展开并更新标记信息
|
||||
let remark = measureData.data.data.remark
|
||||
let i = this.questions.findIndex(i=>i.part === remark)
|
||||
this.questions[i].suvMax = measureData.suvMax
|
||||
this.questions[i].annotation = measureData
|
||||
this.expands = [this.questions[i].rowIndex]
|
||||
} else {
|
||||
if (this.expands.length > 0) {
|
||||
let i = this.questions.findIndex(i=>i.rowIndex === this.expands[0])
|
||||
if (measureData.data && measureData.data.data) {
|
||||
measureData.data.data.remark = this.questions[i].part
|
||||
}
|
||||
|
||||
this.questions[i].annotation = measureData
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.measurement-wrapper{
|
||||
|
||||
// .container{
|
||||
// padding: 10px;
|
||||
// }
|
||||
.title{
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
color: #ddd;
|
||||
font-size: 15px;
|
||||
|
||||
}
|
||||
.add-icon{
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
color: #ddd;
|
||||
font-size: 15px;
|
||||
border: 1px solid #938b8b;
|
||||
margin-bottom: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.add-icon:hover{
|
||||
background-color: #607d8b;
|
||||
}
|
||||
|
||||
.flex-row{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: #424242;
|
||||
|
||||
}
|
||||
.lesion_list{
|
||||
position: relative;
|
||||
::v-deep .el-table, .el-table__expanded-cell {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
border-color:#444444;
|
||||
.el-form-item__label {
|
||||
color: #fff;
|
||||
}
|
||||
.el-input__inner {
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
border-color: #606266;
|
||||
color: #fff;
|
||||
}
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
background-color: #303133;
|
||||
border-color: #444444;
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
::v-deep .el-table th, .el-table tr {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
border-color:#444444;
|
||||
}
|
||||
::v-deep .el-table__body tr > td{
|
||||
background-color:#000 !important;
|
||||
color: #fff;
|
||||
border-color:#444444;
|
||||
}
|
||||
// ::v-deep .el-table__body tr:hover > td{
|
||||
// background-color:#858282 !important;
|
||||
// color: #fff;
|
||||
// border-color:#444444;
|
||||
// }
|
||||
::v-deep .el-table--border th.gutter:last-of-type{
|
||||
border: none;
|
||||
}
|
||||
::v-deep .el-card__header{
|
||||
border: none;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
.el-collapse{
|
||||
border-bottom:none;
|
||||
border-top:none;
|
||||
::v-deep .el-collapse-item{
|
||||
background-color: #000!important;
|
||||
color: #ddd;
|
||||
|
||||
}
|
||||
::v-deep .el-collapse-item__header{
|
||||
background-color: #000!important;
|
||||
color: #ddd;
|
||||
border-bottom-color:#5a5a5a;
|
||||
padding-left: 5px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
}
|
||||
::v-deep .el-collapse-item__wrap{
|
||||
background-color: #000!important;
|
||||
color: #ddd;
|
||||
}
|
||||
::v-deep .el-collapse-item__content{
|
||||
width:260px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
// border: 1px solid #ffeb3b;
|
||||
border: 1px solid #fff;
|
||||
z-index: 1;
|
||||
color: #ddd;
|
||||
padding: 5px;
|
||||
background-color:#1e1e1e;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,690 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
class="viewport_container"
|
||||
:class="['item',activeIndex === index?'item_active':'']"
|
||||
@mouseup="sliderMouseup"
|
||||
>
|
||||
<div :id="`viewport${index}`" ref="viewportCanvas" style="height: 100%;width:100%" />
|
||||
<!-- 序列信息 -->
|
||||
<div
|
||||
v-if="seriesInfo.taskBlindName"
|
||||
class="seriesInfo_wrapper"
|
||||
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
||||
>
|
||||
<h2 v-if="isReadingShowSubjectInfo" class="taskInfo_container">
|
||||
{{ subjectCode }} {{ seriesInfo.taskBlindName }}
|
||||
</h2>
|
||||
<div v-if="index === 1 || index === 2">
|
||||
Series: #{{ seriesInfo.seriesNumber }}
|
||||
</div>
|
||||
<div v-if="index !== 4">Image: #{{ `${seriesInfo.imageIdIndex + 1} / ${seriesInfo.imageMaxLength}` }}</div>
|
||||
<div v-if="index === 1 || index === 2">{{ seriesInfo.modality }}</div>
|
||||
</div>
|
||||
<!-- 描述信息 -->
|
||||
<div
|
||||
v-if="seriesInfo.description && (index === 1 || index === 2)"
|
||||
class="descInfo_wrapper"
|
||||
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
||||
>
|
||||
<div>{{ seriesInfo.description }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 图像信息 -->
|
||||
<div
|
||||
v-if="seriesInfo.description && (index === 1 || index === 2 || index === 3)"
|
||||
class="imageInfo_wrapper_l"
|
||||
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
||||
>
|
||||
<div v-show="mousePosition.index.length > 0">
|
||||
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
|
||||
</div>
|
||||
<div v-if="(seriesInfo.modality === 'CT' || seriesInfo.modality === 'DR' || seriesInfo.modality === 'CR') && mousePosition.value">
|
||||
HU: {{ mousePosition.value }}
|
||||
</div>
|
||||
<div v-else-if="(seriesInfo.modality === 'PT' && mousePosition.value)">
|
||||
SUVbw(g/ml): {{ digitPlaces === -1 ?mousePosition.value.toFixed(3) :mousePosition.value.toFixed(digitPlaces) }}
|
||||
</div>
|
||||
<div v-else-if="mousePosition.value">
|
||||
Density: {{ mousePosition.value }}
|
||||
</div>
|
||||
<div v-if="index === 1 || index === 2">
|
||||
W*H: {{ imageInfo.size }}
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="index === 1 || index === 2">Zoom: {{ imageInfo.zoom }}</div> -->
|
||||
</div>
|
||||
<div
|
||||
v-if="seriesInfo.description && (index === 1 || index === 2 || index === 3)"
|
||||
class="imageInfo_wrapper_r"
|
||||
:style="{color:index%2 == 1 ? '#ddd' : '#666'}"
|
||||
>
|
||||
<div v-show="imageInfo.location && index !== 3 ">Location: {{ `${imageInfo.location} mm` }}</div>
|
||||
<div v-show="seriesInfo.sliceThickness && index !== 3">Slice Thickness: {{ `${Number(seriesInfo.sliceThickness).toFixed(2)} mm` }}</div>
|
||||
<div v-show="imageInfo.wwwc ">WW/WL: {{ imageInfo.wwwc }}</div>
|
||||
</div>
|
||||
<div v-if="index !== 4" ref="sliderBox" class="slider_box" :style="{background: index === 2?'#ddd':'#333'}" @click.stop="goViewer($event)">
|
||||
<div :style="{top: sliderBoxHeight + '%'}" class="box" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
|
||||
</div>
|
||||
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
|
||||
{{ markers.top }}
|
||||
</div>
|
||||
<div style="position: absolute;top: 50%;right: 15px;color: #f44336;transform: translateY(-50%);">
|
||||
{{ markers.right }}
|
||||
</div>
|
||||
|
||||
<div style="position: absolute;left: 50%;bottom: 30px;color: #f44336;transform: translateX(-50%);">
|
||||
{{ markers.bottom }}
|
||||
</div>
|
||||
<div style="position: absolute;top: 50%;left: 15px;color: #f44336;transform: translateY(-50%);">
|
||||
{{ markers.left }}
|
||||
</div>
|
||||
<div v-if="presetName" class="color_bar">
|
||||
<canvas id="colorBar_Canvas" />
|
||||
</div>
|
||||
<div v-if="index === 4" id="rotateBar" ref="rotateBar" class="rotate_slider_box" @click.stop="clickRotate($event)">
|
||||
<div id="rotateSlider" :style="{left: rotateBarLeft + 'px'}" class="box" @click.stop.prevent="() => {return}" @mousedown.stop="rotateBarMousedown($event)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {
|
||||
getRenderingEngine,
|
||||
metaData,
|
||||
utilities,
|
||||
// getOrCreateCanvas,
|
||||
Enums } from '@cornerstonejs/core'
|
||||
|
||||
import {
|
||||
utilities as toolsUtilities,
|
||||
annotation,
|
||||
Enums as toolsEnums
|
||||
// cursors
|
||||
} from '@cornerstonejs/tools'
|
||||
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
|
||||
import { vec3, mat4 } from 'gl-matrix'
|
||||
import html2canvas from 'html2canvas'
|
||||
const { getColormap } = utilities.colormap
|
||||
const {
|
||||
VOLUME_NEW_IMAGE
|
||||
// VOLUME_LOADED,
|
||||
// IMAGE_VOLUME_MODIFIED,
|
||||
// VOLUME_VIEWPORT_NEW_VOLUME
|
||||
} = Enums.Events
|
||||
|
||||
var renderingEngine
|
||||
var viewport
|
||||
export default {
|
||||
name: 'Viewport',
|
||||
props: {
|
||||
activeIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
index: {
|
||||
// 1:ct 2:pet 3:fusion 4:mip
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isReadingShowSubjectInfo: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
seriesInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
renderingEngineId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
viewportId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
volume: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
measureDatas: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
subjectCode: '',
|
||||
mousePosition: { index: [], value: null, modalityUnit: '', world: [], ctVal: null, ptVal: null },
|
||||
digitPlaces: -1,
|
||||
imageInfo: {
|
||||
zoom: null,
|
||||
size: null,
|
||||
location: null,
|
||||
sliceThickness: null,
|
||||
wwwc: null
|
||||
},
|
||||
sliderBoxHeight: 0,
|
||||
|
||||
sliderInfo: {
|
||||
oldB: null,
|
||||
oldM: null,
|
||||
isMove: false
|
||||
},
|
||||
rotateAngle: 0,
|
||||
rotateBarLeft: 0,
|
||||
rotateBarInfo: {
|
||||
initX: null,
|
||||
initLeft: null,
|
||||
isMove: false
|
||||
},
|
||||
isFirstRender: true,
|
||||
defaultWindowLevel: {},
|
||||
presetName: '',
|
||||
orientationMarkers: [],
|
||||
originalMarkers: [],
|
||||
markers: { top: '', right: '', bottom: '', left: '' }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeIndex: {
|
||||
handler(v) {
|
||||
console.log('activeIndex ', v)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const digitPlaces = parseInt(this.$route.query.digitPlaces)
|
||||
this.digitPlaces = digitPlaces === -1 ? 2 : digitPlaces
|
||||
// console.log(toolsUtilities)
|
||||
this.subjectCode = this.$route.query.subjectCode
|
||||
var element = document.getElementById(`viewport${this.index}`)
|
||||
|
||||
element.addEventListener(VOLUME_NEW_IMAGE, this.handleVolumeNewImage)
|
||||
element.addEventListener(Enums.Events.CAMERA_MODIFIED, this.handleCameraModified)
|
||||
element.addEventListener(Enums.Events.VOI_MODIFIED, this.handleVOIModified)
|
||||
element.addEventListener(toolsEnums.Events.MOUSE_WHEEL, this.handletoolsMouseWheel)
|
||||
|
||||
element.addEventListener('CORNERSTONE_TOOLS_MOUSE_MOVE', this.handleMouseMove)
|
||||
|
||||
element.addEventListener('mouseleave', this.handleMouseLeave)
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
this.sliderMouseup()
|
||||
if (this.index === 4) {
|
||||
this.rotateBarMouseup()
|
||||
}
|
||||
})
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
this.sliderMousemove(e)
|
||||
if (this.index === 4) {
|
||||
this.rotateBarMousemove(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleVolumeNewImage(e) {
|
||||
const { imageIndex } = e.detail
|
||||
if (this.viewportId === e.detail.viewportId && this.index !== 4) {
|
||||
this.seriesInfo.imageIdIndex = imageIndex
|
||||
}
|
||||
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
viewport = renderingEngine.getViewport(this.viewportId)
|
||||
|
||||
if (viewport) {
|
||||
var zoom = viewport.getZoom()
|
||||
|
||||
if (zoom) {
|
||||
this.imageInfo.zoom = zoom.toFixed(4)
|
||||
}
|
||||
var imageId = viewport.getCurrentImageId()
|
||||
if (imageId) {
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
|
||||
this.imageInfo.size = `${imagePlaneModule.columns}*${imagePlaneModule.rows}`
|
||||
this.imageInfo.location = imagePlaneModule.sliceLocation
|
||||
this.getOrientationMarker()
|
||||
this.sliderBoxHeight = imageIndex * 100 / (this.seriesInfo.imageMaxLength - 1)
|
||||
}
|
||||
|
||||
var properties = viewport.getProperties()
|
||||
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const windowWidth = upper - lower
|
||||
const windowCenter = (upper + lower) / 2
|
||||
this.defaultWindowLevel.windowWidth = windowWidth
|
||||
this.defaultWindowLevel.windowCenter = windowCenter
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
}
|
||||
if (this.presetName) {
|
||||
this.renderColorBar(this.presetName)
|
||||
}
|
||||
}
|
||||
},
|
||||
getOrientationMarker() {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { viewUp, viewPlaneNormal } = viewport.getCamera()
|
||||
|
||||
const viewRight = vec3.create()
|
||||
vec3.cross(viewRight, viewUp, viewPlaneNormal)
|
||||
|
||||
const columnCosines = [-viewUp[0], -viewUp[1], -viewUp[2]]
|
||||
const rowCosines = viewRight
|
||||
const rowString = toolsUtilities.orientation.getOrientationStringLPS(rowCosines)
|
||||
const columnString = toolsUtilities.orientation.getOrientationStringLPS(columnCosines)
|
||||
const oppositeRowString = toolsUtilities.orientation.invertOrientationStringLPS(rowString)
|
||||
const oppositeColumnString = toolsUtilities.orientation.invertOrientationStringLPS(columnString)
|
||||
this.markers.top = oppositeColumnString
|
||||
this.markers.right = rowString
|
||||
this.markers.bottom = columnString
|
||||
this.markers.left = oppositeRowString
|
||||
this.orientationMarkers = [oppositeColumnString, rowString, columnString, oppositeRowString]
|
||||
if (this.originalMarkers.length === 0) {
|
||||
this.originalMarkers = [...this.orientationMarkers]
|
||||
}
|
||||
},
|
||||
rotateMarkers(type) {
|
||||
var markers = [...this.orientationMarkers]
|
||||
if (type === 4) {
|
||||
// 左转90度
|
||||
this.orientationMarkers = markers.slice(1, 4).concat(markers[0])
|
||||
} else if (type === 5) {
|
||||
// 右转90度
|
||||
this.orientationMarkers = [markers[3]].concat(markers.slice(0, 3))
|
||||
}
|
||||
this.setMarkers()
|
||||
},
|
||||
resetMarks() {
|
||||
this.orientationMarkers = [...this.originalMarkers]
|
||||
this.setMarkers()
|
||||
},
|
||||
setMarkers() {
|
||||
var markers = [...this.orientationMarkers]
|
||||
for (const key in this.markers) {
|
||||
var v = markers.shift(0)
|
||||
this.markers[key] = v
|
||||
}
|
||||
},
|
||||
handleCameraModified(e) {
|
||||
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (!viewport) return
|
||||
|
||||
var zoom = viewport.getZoom()
|
||||
if (!zoom) return
|
||||
this.imageInfo.zoom = zoom.toFixed(4)
|
||||
// console.log(e)
|
||||
},
|
||||
handleVOIModified(e) {
|
||||
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (!viewport) return
|
||||
|
||||
var properties = viewport.getProperties()
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(
|
||||
lower,
|
||||
upper
|
||||
)
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
}
|
||||
},
|
||||
handleMouseMove(e) {
|
||||
if (this.index !== 4) {
|
||||
const { currentPoints } = e.detail
|
||||
const worldPoint = currentPoints.world
|
||||
const { imageData } = this.volume
|
||||
const index = imageData.worldToIndex(worldPoint)
|
||||
|
||||
index[0] = Math.floor(index[0])
|
||||
index[1] = Math.floor(index[1])
|
||||
index[2] = Math.floor(index[2])
|
||||
this.mousePosition.index = index
|
||||
|
||||
this.mousePosition.value = this.getValue(this.volume, worldPoint)
|
||||
}
|
||||
},
|
||||
getValue(volume, worldPos) {
|
||||
const { dimensions, scalarData, imageData } = volume
|
||||
|
||||
const index = imageData.worldToIndex(worldPos)
|
||||
|
||||
index[0] = Math.floor(index[0])
|
||||
index[1] = Math.floor(index[1])
|
||||
index[2] = Math.floor(index[2])
|
||||
|
||||
if (!utilities.indexWithinDimensions(index, dimensions)) {
|
||||
return
|
||||
}
|
||||
|
||||
const yMultiple = dimensions[0]
|
||||
const zMultiple = dimensions[0] * dimensions[1]
|
||||
|
||||
const value = scalarData[index[2] * zMultiple + index[1] * yMultiple + index[0]]
|
||||
|
||||
return value
|
||||
},
|
||||
handleMouseLeave(e) {
|
||||
this.mousePosition.index = []
|
||||
this.mousePosition.value = null
|
||||
},
|
||||
goViewer(e) {
|
||||
var height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
|
||||
this.sliderBoxHeight = height
|
||||
var index = Math.trunc(this.seriesInfo.imageMaxLength * this.sliderBoxHeight / 100)
|
||||
if (this.seriesInfo.imageIdIndex !== index) {
|
||||
this.scroll(index)
|
||||
}
|
||||
},
|
||||
|
||||
sliderMousedown(e) {
|
||||
var boxHeight = this.$refs['sliderBox'].clientHeight
|
||||
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
|
||||
this.sliderInfo.oldM = e.clientY
|
||||
this.sliderInfo.isMove = true
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
sliderMousemove(e) {
|
||||
if (!this.sliderInfo.isMove) return
|
||||
var PX = this.sliderInfo.oldB - (this.sliderInfo.oldM - e.clientY)
|
||||
var boxHeight = this.$refs['sliderBox'].clientHeight
|
||||
// 滚动翻页
|
||||
if (PX < 0) return
|
||||
if (PX > boxHeight) return
|
||||
const height = PX * 100 / boxHeight
|
||||
var index = Math.trunc(this.seriesInfo.imageMaxLength * height / 100)
|
||||
index = index > this.seriesInfo.imageMaxLength ? this.seriesInfo.imageMaxLength : index < 0 ? 0 : index
|
||||
this.sliderBoxHeight = height
|
||||
if (this.seriesInfo.imageIdIndex !== index) {
|
||||
this.scroll(index)
|
||||
}
|
||||
},
|
||||
handletoolsMouseWheel(e) {
|
||||
const { viewportId, wheel } = e.detail
|
||||
if (viewportId === 'PET_MIP_CORONAL') {
|
||||
const container = document.getElementById('rotateBar')
|
||||
const slider = document.getElementById('rotateSlider')
|
||||
const containerWidth = container.offsetWidth
|
||||
const sliderWidth = slider.offsetWidth
|
||||
const maxX = containerWidth - sliderWidth
|
||||
const { direction } = wheel
|
||||
|
||||
var x = Math.trunc(30 * ((containerWidth - sliderWidth) / 360))
|
||||
if (direction > 0 && (this.rotateBarLeft + x) > maxX) {
|
||||
this.rotateBarLeft = x - (containerWidth - sliderWidth - this.rotateBarLeft)
|
||||
} else if (direction < 0 && (this.rotateBarLeft < x)) {
|
||||
this.rotateBarLeft = containerWidth - (x - this.rotateBarLeft + sliderWidth)
|
||||
} else {
|
||||
this.rotateBarLeft = x * direction + this.rotateBarLeft
|
||||
}
|
||||
}
|
||||
},
|
||||
rotateBarMousemove(e) {
|
||||
// 滚动旋转
|
||||
if (!this.rotateBarInfo.isMove) return
|
||||
const container = document.getElementById('rotateBar')
|
||||
const slider = document.getElementById('rotateSlider')
|
||||
const containerWidth = container.offsetWidth
|
||||
const sliderWidth = slider.offsetWidth
|
||||
let x = Math.trunc(e.clientX - this.rotateBarInfo.initX + this.rotateBarInfo.initLeft)
|
||||
if (x < 0) x = 0
|
||||
if (x > containerWidth - sliderWidth) x = containerWidth - sliderWidth
|
||||
const deltaX = x - this.rotateBarLeft
|
||||
const angle = Math.sin((deltaX * (360 / (containerWidth - sliderWidth))) * Math.PI / 180)
|
||||
this.rotate(angle)
|
||||
this.rotateBarLeft = x
|
||||
},
|
||||
rotateBarMousedown(e) {
|
||||
console.log('rotateBarMousedown')
|
||||
this.rotateBarInfo.initLeft = e.srcElement.offsetLeft
|
||||
this.rotateBarInfo.initX = e.clientX
|
||||
this.rotateBarInfo.isMove = true
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
rotate(angle) {
|
||||
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const camera = viewport.getCamera()
|
||||
const { viewUp, position, focalPoint } = camera
|
||||
const [cx, cy, cz] = focalPoint
|
||||
const [ax, ay, az] = [0, 0, 1]
|
||||
const newPosition = [0, 0, 0]
|
||||
const newFocalPoint = [0, 0, 0]
|
||||
const newViewUp = [0, 0, 0]
|
||||
|
||||
const transform = mat4.identity(new Float32Array(16))
|
||||
mat4.translate(transform, transform, [cx, cy, cz])
|
||||
mat4.rotate(transform, transform, angle, [ax, ay, az])
|
||||
mat4.translate(transform, transform, [-cx, -cy, -cz])
|
||||
vec3.transformMat4(newPosition, position, transform)
|
||||
vec3.transformMat4(newFocalPoint, focalPoint, transform)
|
||||
|
||||
mat4.identity(transform)
|
||||
mat4.rotate(transform, transform, angle, [ax, ay, az])
|
||||
vec3.transformMat4(newViewUp, viewUp, transform)
|
||||
|
||||
viewport.setCamera({
|
||||
position: newPosition,
|
||||
viewUp: newViewUp,
|
||||
focalPoint: newFocalPoint
|
||||
})
|
||||
|
||||
viewport.render()
|
||||
},
|
||||
clickRotate(e) {
|
||||
// console.log('clickRotate')
|
||||
const container = document.getElementById('rotateBar')
|
||||
const containerWidth = container.offsetWidth
|
||||
const slider = document.getElementById('rotateSlider')
|
||||
const sliderWidth = slider.offsetWidth
|
||||
const x = Math.trunc(e.offsetX)
|
||||
const deltaX = x - this.rotateBarLeft
|
||||
const angle = Math.sin((deltaX * (360 / (containerWidth - sliderWidth))) * Math.PI / 180)
|
||||
this.rotate(angle)
|
||||
this.rotateBarLeft = x
|
||||
},
|
||||
async scroll(index) {
|
||||
renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const actorEntries = viewport.getActors()
|
||||
|
||||
if (!actorEntries) {
|
||||
return
|
||||
}
|
||||
// const delta = index - this.seriesInfo.imageIdIndex
|
||||
// toolsUtilities.scroll(viewport, {
|
||||
// delta,
|
||||
// volumeId: actorEntries.uid
|
||||
// })
|
||||
await utilities.jumpToSlice(viewport.element, {
|
||||
imageIndex: index,
|
||||
});
|
||||
renderingEngine.render()
|
||||
},
|
||||
rotateBarMouseup(e) {
|
||||
this.rotateBarInfo.isMove = false
|
||||
},
|
||||
sliderMouseup(e) {
|
||||
this.sliderInfo.isMove = false
|
||||
},
|
||||
setAnnotation(imageId, element) {
|
||||
this.measureDatas.forEach(item => {
|
||||
if (item.OtherMeasureData) {
|
||||
var { metadata, annotationUID } = item.OtherMeasureData
|
||||
var { referencedImageId } = metadata
|
||||
console.log(annotationUID, annotation.state.getAnnotation(annotationUID))
|
||||
if (!annotation.state.getAnnotation(annotationUID) && referencedImageId === imageId) {
|
||||
annotation.state.addAnnotation(item.OtherMeasureData, element)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
setPreset(presetName) {
|
||||
this.presetName = presetName
|
||||
},
|
||||
renderColorBar(presetName) {
|
||||
var colorMap = null
|
||||
if (presetName === 'hsv') {
|
||||
colorMap = vtkColorMaps.getPresetByName(presetName)
|
||||
} else {
|
||||
colorMap = getColormap(presetName)
|
||||
}
|
||||
const rgbPoints = colorMap.RGBPoints
|
||||
const canvas = document.getElementById('colorBar_Canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
const canvasWidth = 160
|
||||
const canvasHeight = 5
|
||||
const rectWidth = 160
|
||||
const rectHeight = canvasHeight
|
||||
canvas.width = canvasWidth
|
||||
canvas.height = canvasHeight
|
||||
const gradient = ctx.createLinearGradient(0, 0, rectWidth, 0)
|
||||
for (let i = 0; i < rgbPoints.length; i += 4) {
|
||||
let position = 0
|
||||
if (rgbPoints[0] === -1) {
|
||||
position = (rgbPoints[i] + 1) / 2
|
||||
} else {
|
||||
position = rgbPoints[i]
|
||||
}
|
||||
const color = `rgb(${parseInt(rgbPoints[i + 1] * 255)}, ${parseInt(rgbPoints[i + 2] * 255)}, ${parseInt(rgbPoints[i + 3] * 255)})`
|
||||
gradient.addColorStop(position, color)
|
||||
}
|
||||
ctx.fillStyle = gradient
|
||||
ctx.fillRect(0, 0, rectWidth, rectHeight)
|
||||
},
|
||||
setWwWc() {
|
||||
var properties = viewport.getProperties()
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const windowWidth = upper - lower
|
||||
const windowCenter = (upper + lower) / 2
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
}
|
||||
},
|
||||
async getScreenshots() {
|
||||
const divForDownloadViewport = document.querySelector(
|
||||
`div[data-viewport-uid="FUSION_AXIAL"]`
|
||||
)
|
||||
// const divForDownloadViewport = document.querySelector(
|
||||
// '.viewport_container'
|
||||
// )
|
||||
|
||||
var canvas = await html2canvas(divForDownloadViewport)
|
||||
var pictureBaseStr = canvas.toDataURL('image/png', 1)
|
||||
return pictureBaseStr
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.viewport_container{
|
||||
width:100%;
|
||||
height: 100%;
|
||||
.seriesInfo_wrapper {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
.taskInfo_container{
|
||||
color:#f44336;
|
||||
padding: 5px 0px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.descInfo_wrapper{
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 10px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
}
|
||||
.imageInfo_wrapper_l{
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 5px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
}
|
||||
.imageInfo_wrapper_r{
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
bottom: 5px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
}
|
||||
.slider_box{
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
height: calc(100% - 120px);
|
||||
transform: translateY(-50%);
|
||||
top: calc(50% - 30px);
|
||||
width: 10px;
|
||||
background: #333;
|
||||
cursor: pointer;
|
||||
.box{
|
||||
z-index:10;
|
||||
background: #9e9e9e;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: move
|
||||
}
|
||||
}
|
||||
.color_bar{
|
||||
|
||||
position: absolute;
|
||||
|
||||
// transform:translateY(-50%);
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: right;
|
||||
left: -150px;
|
||||
top: 30%;
|
||||
// transform-origin: top left;
|
||||
z-index: 1;
|
||||
// background: #f44336;
|
||||
}
|
||||
.rotate_slider_box{
|
||||
position: absolute;
|
||||
width: 380px;
|
||||
height: 10px;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #333;
|
||||
cursor: pointer;
|
||||
.box{
|
||||
z-index:10;
|
||||
background: #9e9e9e;
|
||||
height: 100%;
|
||||
width: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: move
|
||||
}
|
||||
}
|
||||
// .my_slider_box:after{
|
||||
// content: '';
|
||||
// position: absolute;
|
||||
// bottom: -20px;
|
||||
// left: 0;
|
||||
// height: 20px;
|
||||
// width: 100%;
|
||||
// background: #333;
|
||||
// }
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,10 +1,31 @@
|
|||
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
|
||||
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
|
||||
import {
|
||||
getImageTypeSubItemFromDataset,
|
||||
extractOrientationFromDataset,
|
||||
extractPositionFromDataset,
|
||||
extractSpacingFromDataset,
|
||||
extractSliceThicknessFromDataset,
|
||||
} from "@/utils/extractPositioningFromDataset";
|
||||
import getNumberValues from "@/utils/getNumberValues";
|
||||
import isNMReconstructable from "@/utils/isNMReconstructable";
|
||||
function getSpacingBetweenSlices(dataSet) {
|
||||
if (dataSet?.elements?.x00180088) {
|
||||
return dataSet.floatString("x00180088");
|
||||
}
|
||||
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
|
||||
if (
|
||||
pixelMeasuresSequence?.items?.length &&
|
||||
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
|
||||
) {
|
||||
return pixelMeasuresSequence.items[0].dataSet.floatString("x00180088");
|
||||
}
|
||||
}
|
||||
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(":");
|
||||
|
||||
let url = imageId.substring(firstColonIndex + 1);
|
||||
const frameIndex = url.indexOf('frame=');
|
||||
const frameIndex = url.indexOf("frame=");
|
||||
|
||||
let frame;
|
||||
|
||||
|
|
@ -21,127 +42,112 @@ function parseImageId(imageId) {
|
|||
frame,
|
||||
};
|
||||
}
|
||||
function getNumberValues(dataSet, tag, minimumLength) {
|
||||
const values = [];
|
||||
const valueAsString = dataSet.string(tag);
|
||||
|
||||
if (!valueAsString) {
|
||||
function metaDataProvider(type, imageId) {
|
||||
const parsedImageId = parseImageId(imageId);
|
||||
const dataSet = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.get(
|
||||
parsedImageId.url
|
||||
);
|
||||
if (!dataSet) {
|
||||
return;
|
||||
}
|
||||
const split = valueAsString.split('\\');
|
||||
if (type === "imagePlaneModule") {
|
||||
const frameIndex = parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
|
||||
// const imageOrientationPatient = extractOrientationFromDataset(dataSet, frameIndex);
|
||||
// let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
|
||||
// const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
|
||||
// const sliceThickness = extractSliceThicknessFromDataset(dataSet, frameIndex);
|
||||
const imageOrientationPatient = extractOrientationFromDataset(dataSet);
|
||||
let imagePositionPatient = extractPositionFromDataset(dataSet);
|
||||
const pixelSpacing = extractSpacingFromDataset(dataSet);
|
||||
const sliceThickness = extractSliceThicknessFromDataset(dataSet);
|
||||
|
||||
const modality = dataSet.string("x00080060");
|
||||
if (
|
||||
modality &&
|
||||
modality.includes("NM") &&
|
||||
parsedImageId.frame !== undefined &&
|
||||
parsedImageId.frame > 1
|
||||
) {
|
||||
// 坐标堆叠 (位置相同) :NM 多帧 DICOM 的空间位置 ( imagePositionPatient ) 通常只存在于 Detector Information Sequence 序列的第一个 Item 中。原来的代码在提取位置时,所有帧都被赋予了第一帧的位置。因为所有帧都挤在一个坐标上,3D 引擎无法根据厚度和法向量空间展开它们,导致间距 (Spacing) 为 0
|
||||
// 当识别到 NM 多帧图像且属于多帧切片时,通过 RowCosines 与 ColumnCosines 的叉乘计算出切片的 法向量(Normal) ,并结合 帧索引 (frameIndex) 以及 Spacing Between Slices (0018,0088)(层间距) 或者 Slice Thickness (0018,0050)(层厚) ,自动推算补全后续每一帧的 imagePositionPatient 真实三维坐标
|
||||
//
|
||||
if (
|
||||
imageOrientationPatient &&
|
||||
imagePositionPatient &&
|
||||
sliceThickness !== undefined
|
||||
) {
|
||||
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
|
||||
const step =
|
||||
spacingBetweenSlices !== undefined ? spacingBetweenSlices : sliceThickness;
|
||||
const rowCosines = [
|
||||
parseFloat(imageOrientationPatient[0]),
|
||||
parseFloat(imageOrientationPatient[1]),
|
||||
parseFloat(imageOrientationPatient[2]),
|
||||
];
|
||||
const columnCosines = [
|
||||
parseFloat(imageOrientationPatient[3]),
|
||||
parseFloat(imageOrientationPatient[4]),
|
||||
parseFloat(imageOrientationPatient[5]),
|
||||
];
|
||||
const normal = [
|
||||
rowCosines[1] * columnCosines[2] - rowCosines[2] * columnCosines[1],
|
||||
rowCosines[2] * columnCosines[0] - rowCosines[0] * columnCosines[2],
|
||||
rowCosines[0] * columnCosines[1] - rowCosines[1] * columnCosines[0],
|
||||
];
|
||||
|
||||
if (minimumLength && split.length < minimumLength) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
values.push(parseFloat(split[i]));
|
||||
}
|
||||
const offset = frameIndex * parseFloat(step);
|
||||
|
||||
return values;
|
||||
}
|
||||
function getImageTypeSubItemFromDataset(dataSet, index) {
|
||||
const imageType = dataSet.string('x00080008');
|
||||
if (imageType) {
|
||||
const subTypes = imageType.split('\\');
|
||||
if (subTypes.length > index) {
|
||||
return subTypes[index];
|
||||
imagePositionPatient = [
|
||||
parseFloat(imagePositionPatient[0]) + normal[0] * offset,
|
||||
parseFloat(imagePositionPatient[1]) + normal[1] * offset,
|
||||
parseFloat(imagePositionPatient[2]) + normal[2] * offset,
|
||||
];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function isNMReconstructable(imageSubType) {
|
||||
return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO';
|
||||
}
|
||||
function extractOrientationFromNMMultiframeDataset(dataSet) {
|
||||
let imageOrientationPatient;
|
||||
const modality = dataSet.string('x00080060');
|
||||
if (modality?.includes('NM')) {
|
||||
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
|
||||
if (imageSubType && isNMReconstructable(imageSubType)) {
|
||||
if (dataSet.elements.x00540022) {
|
||||
imageOrientationPatient = getNumberValues(dataSet.elements.x00540022.items[0].dataSet, 'x00200037', 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
return imageOrientationPatient;
|
||||
}
|
||||
function extractOrientationFromDataset(dataSet) {
|
||||
let imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
||||
if (!imageOrientationPatient && dataSet.elements.x00209116) {
|
||||
imageOrientationPatient = getNumberValues(dataSet.elements.x00209116.items[0].dataSet, 'x00200037', 6);
|
||||
}
|
||||
if (!imageOrientationPatient) {
|
||||
imageOrientationPatient =
|
||||
extractOrientationFromNMMultiframeDataset(dataSet);
|
||||
}
|
||||
return imageOrientationPatient;
|
||||
}
|
||||
function extractPositionFromNMMultiframeDataset(dataSet) {
|
||||
let imagePositionPatient;
|
||||
const modality = dataSet.string('x00080060');
|
||||
if (modality?.includes('NM')) {
|
||||
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
|
||||
if (imageSubType && isNMReconstructable(imageSubType)) {
|
||||
if (dataSet.elements.x00540022) {
|
||||
imagePositionPatient = getNumberValues(dataSet.elements.x00540022.items[0].dataSet, 'x00200032', 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
return imagePositionPatient;
|
||||
}
|
||||
function extractPositionFromDataset(dataSet) {
|
||||
let imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||
if (!imagePositionPatient && dataSet.elements.x00209113) {
|
||||
imagePositionPatient = getNumberValues(dataSet.elements.x00209113.items[0].dataSet, 'x00200032', 3);
|
||||
}
|
||||
if (!imagePositionPatient) {
|
||||
imagePositionPatient = extractPositionFromNMMultiframeDataset(dataSet);
|
||||
}
|
||||
return imagePositionPatient;
|
||||
}
|
||||
function extractSliceThicknessFromDataset(dataSet) {
|
||||
let sliceThickness;
|
||||
if (dataSet.elements.x00180050) {
|
||||
sliceThickness = dataSet.floatString('x00180050');
|
||||
}
|
||||
else if (dataSet.elements.x00289110 &&
|
||||
dataSet.elements.x00289110.items.length &&
|
||||
dataSet.elements.x00289110.items[0].dataSet.elements.x00180050) {
|
||||
sliceThickness =
|
||||
dataSet.elements.x00289110.items[0].dataSet.floatString('x00180050');
|
||||
}
|
||||
return sliceThickness;
|
||||
}
|
||||
function extractSpacingFromDataset(dataSet) {
|
||||
let rowPixelSpacing = null
|
||||
let columnPixelSpacing = null
|
||||
let pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
|
||||
const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2);
|
||||
const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2);
|
||||
if (pixelSpacing) {
|
||||
rowPixelSpacing = pixelSpacing[0]
|
||||
columnPixelSpacing = pixelSpacing[1]
|
||||
}
|
||||
|
||||
// const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
||||
// const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||
// const pixelSpacing = extractSpacingFromDataset(dataSet);
|
||||
const imagePixelSpacing = getNumberValues(dataSet, "x00181164", 2);
|
||||
|
||||
// let rowCosines = null;
|
||||
|
||||
// let columnCosines = null;
|
||||
|
||||
// if (imageOrientationPatient) {
|
||||
// rowCosines = [
|
||||
// parseFloat(imageOrientationPatient[0]),
|
||||
// parseFloat(imageOrientationPatient[1]),
|
||||
// parseFloat(imageOrientationPatient[2]),
|
||||
// ];
|
||||
// columnCosines = [
|
||||
// parseFloat(imageOrientationPatient[3]),
|
||||
// parseFloat(imageOrientationPatient[4]),
|
||||
// parseFloat(imageOrientationPatient[5]),
|
||||
// ];
|
||||
// }
|
||||
const estimatedRadiographicMagnificationFactor = getNumberValues(
|
||||
dataSet,
|
||||
"x00181114",
|
||||
2
|
||||
);
|
||||
let columnPixelSpacing = null;
|
||||
|
||||
let rowPixelSpacing = null;
|
||||
|
||||
if (pixelSpacing) {
|
||||
rowPixelSpacing = pixelSpacing[0];
|
||||
columnPixelSpacing = pixelSpacing[1];
|
||||
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
|
||||
rowPixelSpacing = imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
|
||||
columnPixelSpacing = imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
|
||||
rowPixelSpacing =
|
||||
imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
|
||||
columnPixelSpacing =
|
||||
imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
|
||||
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
|
||||
rowPixelSpacing = imagePixelSpacing[0];
|
||||
columnPixelSpacing = imagePixelSpacing[1];
|
||||
}
|
||||
return rowPixelSpacing !== null ? [rowPixelSpacing, columnPixelSpacing] : undefined
|
||||
}
|
||||
function metaDataProvider(type, imageId) {
|
||||
const parsedImageId = parseImageId(imageId);
|
||||
const dataSet = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url)
|
||||
|
||||
|
||||
if (!dataSet) {
|
||||
return;
|
||||
}
|
||||
if (type === 'imagePlaneModule') {
|
||||
const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
|
||||
const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
|
||||
const pixelSpacing = extractSpacingFromDataset(dataSet);
|
||||
|
||||
let rowCosines = null;
|
||||
|
||||
|
|
@ -160,34 +166,35 @@ function metaDataProvider(type, imageId) {
|
|||
];
|
||||
}
|
||||
return {
|
||||
frameOfReferenceUID: dataSet.string('x00200052'),
|
||||
rows: dataSet.uint16('x00280010'),
|
||||
columns: dataSet.uint16('x00280011'),
|
||||
frameOfReferenceUID: dataSet.string("x00200052"),
|
||||
rows: dataSet.uint16("x00280010"),
|
||||
columns: dataSet.uint16("x00280011"),
|
||||
imageOrientationPatient,
|
||||
rowCosines,
|
||||
columnCosines,
|
||||
imagePositionPatient,
|
||||
sliceThickness: dataSet.floatString('x00180050'),
|
||||
sliceLocation: dataSet.floatString('x00201041'),
|
||||
pixelSpacing: pixelSpacing,
|
||||
rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : null,
|
||||
columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : null,
|
||||
sliceThickness,
|
||||
sliceLocation: dataSet.floatString("x00201041"),
|
||||
pixelSpacing,
|
||||
rowPixelSpacing,
|
||||
columnPixelSpacing,
|
||||
};
|
||||
}
|
||||
if (type === 'nmMultiframeGeometryModule') {
|
||||
const modality = dataSet.string('x00080060');
|
||||
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
|
||||
return {
|
||||
modality,
|
||||
imageType: dataSet.string('x00080008'),
|
||||
imageSubType,
|
||||
imageOrientationPatient: extractOrientationFromDataset(dataSet),
|
||||
imagePositionPatient: extractPositionFromDataset(dataSet),
|
||||
sliceThickness: extractSliceThicknessFromDataset(dataSet),
|
||||
pixelSpacing: extractSpacingFromDataset(dataSet),
|
||||
numberOfFrames: dataSet.uint16('x00280008'),
|
||||
isNMReconstructable: isNMReconstructable(imageSubType) && modality.includes('NM'),
|
||||
};
|
||||
if (type === "nmMultiframeGeometryModule") {
|
||||
const modality = dataSet.string("x00080060");
|
||||
const imageSubType = getImageTypeSubItemFromDataset(dataSet, 2);
|
||||
return {
|
||||
modality,
|
||||
imageType: dataSet.string("x00080008"),
|
||||
imageSubType,
|
||||
imageOrientationPatient: extractOrientationFromDataset(dataSet),
|
||||
imagePositionPatient: extractPositionFromDataset(dataSet),
|
||||
sliceThickness: extractSliceThicknessFromDataset(dataSet),
|
||||
pixelSpacing: extractSpacingFromDataset(dataSet),
|
||||
numberOfFrames: dataSet.uint16("x00280008"),
|
||||
isNMReconstructable:
|
||||
isNMReconstructable(imageSubType) && modality.includes("NM"),
|
||||
};
|
||||
}
|
||||
}
|
||||
export default metaDataProvider;
|
||||
export default metaDataProvider;
|
||||
|
|
|
|||
|
|
@ -1319,12 +1319,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -1335,7 +1335,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1343,7 +1343,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1354,7 +1354,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,9 +112,8 @@
|
|||
</el-input>
|
||||
<el-input
|
||||
v-else
|
||||
type="number"
|
||||
@change="(val) => { formItemChange(val, item) }"
|
||||
@input="(val) => questionForm[item.Id] = val.replace(/[^\d.]/g, '')"
|
||||
@input="numberInput(item.Id)"
|
||||
@blur="handleBlur(questionForm[item.Id], questionForm, item)"
|
||||
v-model.trim="questionForm[item.Id]" :disabled="readingTaskState === 2">
|
||||
<template slot="append" v-if="item.Unit !== 0">{{ item.Unit !== 4 ? $fd('ValueUnit', item.Unit) : item.CustomUnit }}</template>
|
||||
|
|
@ -369,7 +368,7 @@ export default {
|
|||
const type = ['number', 'radio', 'select', 'input', 'textarea', 'calculation']
|
||||
questions.forEach(item => {
|
||||
if (type.includes(item.Type)) {
|
||||
const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Type === 'number' || item.Type === 'calculation' ? isNaN(parseFloat(item.Answer)) ? null : item.Answer : item.Answer
|
||||
const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Answer
|
||||
this.$set(this.questionForm, item.Id, answer)
|
||||
}
|
||||
if (item.QuestionType === 1013) {
|
||||
|
|
@ -498,7 +497,6 @@ export default {
|
|||
|
||||
},
|
||||
formItemChange(val, item) {
|
||||
console.log('formItemChange: ', item.QuestionName, val)
|
||||
this.formChanged = true
|
||||
// if (item.Type === 'number') {
|
||||
// this.limitBlur(item.Id, item.ValueType)
|
||||
|
|
@ -546,6 +544,7 @@ export default {
|
|||
}
|
||||
},
|
||||
numberOrNEInput(id) {
|
||||
this.formChanged = true
|
||||
// this.questionForm[id] = this.questionForm[id].toUpperCase();
|
||||
if (!this.questionForm[id]) {
|
||||
return
|
||||
|
|
@ -564,6 +563,11 @@ export default {
|
|||
this.questionForm[id] = value
|
||||
this.$set(this.questionForm, id, value)
|
||||
},
|
||||
numberInput(id) {
|
||||
this.formChanged = true
|
||||
let value = this.questionForm[id].replace(/[^\d.]/g, '')
|
||||
this.$set(this.questionForm, id, value)
|
||||
},
|
||||
handleNumberOrNEBlur(item) {
|
||||
if (this.questionForm[item.Id] && !isNaN(parseFloat(this.questionForm[item.Id]))) {
|
||||
if (item.ValueType === 3) {
|
||||
|
|
@ -802,7 +806,7 @@ export default {
|
|||
uploadTpl(lesionType, TableName) {
|
||||
this.upload.lesionType = lesionType
|
||||
this.upload.TableName = TableName
|
||||
this.upload.title = `导入( ${this.$fd('LesionType', lesionType)} )`
|
||||
this.upload.title = `${this.$fd('LesionType', lesionType)}`
|
||||
this.upload.visible = true
|
||||
},
|
||||
async downloadTpl() {
|
||||
|
|
@ -834,9 +838,8 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.measurement-wrapper {
|
||||
height: 100%;
|
||||
height: calc(100% - 50px);
|
||||
overflow-y: auto;
|
||||
|
||||
.container {
|
||||
padding: 10px;
|
||||
|
||||
|
|
@ -986,21 +989,21 @@ export default {
|
|||
padding: 5px 0;
|
||||
}
|
||||
|
||||
::v-deep.el-table__fixed-right-patch {
|
||||
::v-deep .el-table__fixed-right-patch {
|
||||
background-color: #000 !important;
|
||||
border-color: #444444;
|
||||
}
|
||||
|
||||
::v-deep.el-table__fixed-body-wrapper tr:hover>td {
|
||||
::v-deep .el-table__fixed-body-wrapper tr:hover>td {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
::v-deep.el-table--scrollable-x .el-table__body-wrapper {
|
||||
::v-deep .el-table--scrollable-x .el-table__body-wrapper {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
}
|
||||
::v-deep.el-tag.el-tag--info {
|
||||
::v-deep .el-tag.el-tag--info {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,11 +56,11 @@ export default {
|
|||
var data = new FormData()
|
||||
data.append('file', param.file)
|
||||
data.append('visitTaskId', this.visitTaskId)
|
||||
data.append('readingImportType', 0)
|
||||
data.append('readingImportType', 3)
|
||||
data.append('TableName', this.TableName)
|
||||
await readingImport(data)
|
||||
this.$emit('close')
|
||||
this.$message.success('导入成功!')
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
loading.close()
|
||||
} catch (e) {
|
||||
loading.close()
|
||||
|
|
|
|||
|
|
@ -1436,12 +1436,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -1452,7 +1452,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1460,7 +1460,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1471,7 +1471,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1436,12 +1436,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -1452,7 +1452,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1460,7 +1460,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1471,7 +1471,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,13 +67,13 @@
|
|||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="readingTaskState < 2" :label="$t('common:action:action')" width="90px"
|
||||
<el-table-column v-if="readingTaskState < 2 && item.LesionType === 104" :label="$t('common:action:action')" width="90px"
|
||||
fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="handleAddOrEdit('edit', item, scope.$index)">
|
||||
{{ $t('common:button:edit') }}
|
||||
</el-button>
|
||||
<el-button v-if="item.LesionType === 112 || item.LesionType === 111" type="text" size="mini"
|
||||
<el-button type="text" size="mini"
|
||||
@click="handleDelete(item, scope.$index)">
|
||||
{{ $t('common:button:delete') }}
|
||||
</el-button>
|
||||
|
|
@ -120,9 +120,8 @@
|
|||
</el-input>
|
||||
<el-input
|
||||
v-else
|
||||
type="number"
|
||||
@change="(val) => { formItemChange(val, item) }"
|
||||
@input="(val) => questionForm[item.Id] = val.replace(/[^\d.]/g, '')"
|
||||
@input="numberInput(item.Id)"
|
||||
@blur="handleBlur(questionForm[item.Id], questionForm, item)"
|
||||
v-model.trim="questionForm[item.Id]" :disabled="readingTaskState === 2">
|
||||
<template slot="append" v-if="item.Unit !== 0">{{ item.Unit !== 4 ? $fd('ValueUnit', item.Unit) : item.CustomUnit }}</template>
|
||||
|
|
@ -369,7 +368,7 @@ export default {
|
|||
const type = ['number', 'radio', 'select', 'input', 'textarea', 'calculation']
|
||||
questions.forEach(item => {
|
||||
if (type.includes(item.Type)) {
|
||||
const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Type === 'number' || item.Type === 'calculation' ? isNaN(parseFloat(item.Answer)) ? null : item.Answer : item.Answer
|
||||
const answer = item.Type === 'select' && item.OptionTypeEnum === 1 && item.Answer ? JSON.parse(item.Answer) : item.Answer
|
||||
this.$set(this.questionForm, item.Id, answer)
|
||||
}
|
||||
if (item.QuestionType === 1013) {
|
||||
|
|
@ -541,6 +540,7 @@ export default {
|
|||
}
|
||||
},
|
||||
numberOrNEInput(id) {
|
||||
this.formChanged = true
|
||||
// this.questionForm[id] = this.questionForm[id].toUpperCase();
|
||||
if (!this.questionForm[id]) {
|
||||
return
|
||||
|
|
@ -559,6 +559,11 @@ export default {
|
|||
this.questionForm[id] = value
|
||||
this.$set(this.questionForm, id, value)
|
||||
},
|
||||
numberInput(id) {
|
||||
this.formChanged = true
|
||||
let value = this.questionForm[id].replace(/[^\d.]/g, '')
|
||||
this.$set(this.questionForm, id, value)
|
||||
},
|
||||
handleNumberOrNEBlur(item) {
|
||||
if (this.questionForm[item.Id] && !this.questionForm[item.Id].startsWith('N')) {
|
||||
if (item.ValueType === 3) {
|
||||
|
|
@ -682,11 +687,27 @@ export default {
|
|||
const m1 = this.qsForm[this.m1Id]
|
||||
const m2 = this.qsForm[this.m2Id]
|
||||
const m3 = this.qsForm[this.m3Id]
|
||||
if (isNaN(parseFloat(m1)) || isNaN(parseFloat(m2)) || isNaN(parseFloat(m3))) {
|
||||
this.$set(this.qsForm, this.avgId, null)
|
||||
} else {
|
||||
const avg = (parseFloat(m1) + parseFloat(m2) + parseFloat(m3)) / 3
|
||||
// if (isNaN(parseFloat(m1)) || isNaN(parseFloat(m2)) || isNaN(parseFloat(m3))) {
|
||||
// this.$set(this.qsForm, this.avgId, null)
|
||||
// } else {
|
||||
// const avg = (parseFloat(m1) + parseFloat(m2) + parseFloat(m3)) / 3
|
||||
// this.$set(this.qsForm, this.avgId, this.numberToFixed(avg))
|
||||
// }
|
||||
//转成有效数字,无效的过滤掉
|
||||
const validNumbers = []
|
||||
if (!isNaN(parseFloat(m1))) validNumbers.push(parseFloat(m1))
|
||||
if (!isNaN(parseFloat(m2))) validNumbers.push(parseFloat(m2))
|
||||
if (!isNaN(parseFloat(m3))) validNumbers.push(parseFloat(m3))
|
||||
|
||||
//只要有效数字的平均值
|
||||
if (validNumbers.length > 0) {
|
||||
// 总和 / 有效数字的个数
|
||||
const sum = validNumbers.reduce((a, b) => a + b, 0)
|
||||
const avg = sum / validNumbers.length
|
||||
this.$set(this.qsForm, this.avgId, this.numberToFixed(avg))
|
||||
} else {
|
||||
// 一个有效数字都没有 → 清空平均值
|
||||
this.$set(this.qsForm, this.avgId, null)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -805,7 +826,7 @@ export default {
|
|||
uploadTpl(lesionType, TableName) {
|
||||
this.upload.lesionType = lesionType
|
||||
this.upload.TableName = TableName
|
||||
this.upload.title = `导入( ${this.$fd('LesionType', lesionType)} )`
|
||||
this.upload.title = `${this.$fd('LesionType', lesionType)}`
|
||||
this.upload.visible = true
|
||||
},
|
||||
async downloadTpl(lesionType) {
|
||||
|
|
@ -813,7 +834,7 @@ export default {
|
|||
const params = {
|
||||
visitTaskId: this.visitTaskId
|
||||
}
|
||||
if (lesionType === 112) {
|
||||
if (lesionType === 104) {
|
||||
await getOCTFCTTemplate(params)
|
||||
} else {
|
||||
await getOCTLipidAngleTemplate(params)
|
||||
|
|
@ -841,7 +862,7 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.measurement-wrapper {
|
||||
height: 100%;
|
||||
height: calc(100% - 50px);
|
||||
overflow-y: auto;
|
||||
// overflow: hidden;
|
||||
|
||||
|
|
@ -994,20 +1015,20 @@ export default {
|
|||
padding: 5px 0;
|
||||
}
|
||||
|
||||
::v-deep.el-table__fixed-right-patch {
|
||||
::v-deep .el-table__fixed-right-patch {
|
||||
background-color: #000 !important;
|
||||
border-color: #444444;
|
||||
}
|
||||
|
||||
::v-deep.el-table__fixed-body-wrapper tr:hover>td {
|
||||
::v-deep .el-table__fixed-body-wrapper tr:hover>td {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
::v-deep.el-table--scrollable-x .el-table__body-wrapper {
|
||||
::v-deep .el-table--scrollable-x .el-table__body-wrapper {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
::v-deep.el-tag.el-tag--info {
|
||||
::v-deep .el-tag.el-tag--info {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,15 +72,17 @@ export default {
|
|||
data.append('file', param.file)
|
||||
data.append('visitTaskId', this.visitTaskId)
|
||||
data.append('TableName', this.TableName)
|
||||
if (this.lesionType === 112) {
|
||||
data.append('readingImportType', 1)
|
||||
await readingImport(data)
|
||||
} else {
|
||||
data.append('readingImportType', 2)
|
||||
await readingImport(data)
|
||||
}
|
||||
data.append('readingImportType', 3)
|
||||
await readingImport(data)
|
||||
// if (this.lesionType === 112) {
|
||||
// data.append('readingImportType', 1)
|
||||
// await readingImport(data)
|
||||
// } else {
|
||||
// data.append('readingImportType', 2)
|
||||
// await readingImport(data)
|
||||
// }
|
||||
this.$emit('close')
|
||||
this.$message.success('导入成功!')
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
loading.close()
|
||||
} catch (e) {
|
||||
loading.close()
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
@change="((val) => { formItemChange(val, question) })" />
|
||||
<!-- 下拉框 -->
|
||||
<el-select v-else-if="question.Type === 'select'" v-model="questionForm[question.Id]"
|
||||
:disabled="readingTaskState >= 2 || ((question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode) || isFirstChangeTask || question.QuestionType === 50 || question.QuestionType === 55"
|
||||
:disabled="readingTaskState >= 2 || ((question.TableQuestionType === 2 || question.QuestionGenre === 2) && !!question.DictionaryCode) || isFirstChangeTask || question.QuestionType === 50 || question.QuestionType === 55 || question.QuestionType === 1026"
|
||||
clearable :multiple="question.OptionTypeEnum === 1" @change="((val) => { formItemChange(val, question) })">
|
||||
<template v-if="question.TableQuestionType === 1">
|
||||
<el-option v-for="item in organList" :key="item.Id" :label="item[question.DataTableColumn]"
|
||||
|
|
|
|||
|
|
@ -234,7 +234,10 @@ export default {
|
|||
obj.forEach(i => {
|
||||
i.IsBaseLineTask = this.isBaseLineTask
|
||||
if (i.Type !== 'group' && i.Type !== 'summary' && i.Id) {
|
||||
const answer = i.Type === 'select' && i.OptionTypeEnum === 1 && i.Answer ? JSON.parse(i.Answer) : i.Answer
|
||||
let answer = i.Type === 'select' && i.OptionTypeEnum === 1 && i.Answer ? JSON.parse(i.Answer) : i.Answer
|
||||
if (i.DictionaryCode && i.Type === 'calculation') {
|
||||
answer = this.$fd(i.DictionaryCode, parseInt(i.Answer))
|
||||
}
|
||||
this.$set(this.questionForm, i.Id, answer ? answer : null)
|
||||
if (i.QuestionType === 44) {
|
||||
// 影响质量评估
|
||||
|
|
|
|||
|
|
@ -614,7 +614,7 @@ export default {
|
|||
|
||||
}
|
||||
|
||||
::v-deep.el-tabs {
|
||||
::v-deep .el-tabs {
|
||||
box-sizing: border-box;
|
||||
padding: 0 5px;
|
||||
height: 100%;
|
||||
|
|
@ -665,7 +665,7 @@ export default {
|
|||
border: 1px solid #607d8b !important;
|
||||
}
|
||||
|
||||
::v-deep.el-progress__text {
|
||||
::v-deep .el-progress__text {
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1203,12 +1203,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -1219,7 +1219,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1227,7 +1227,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1238,7 +1238,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1130,12 +1130,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -1146,7 +1146,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1154,7 +1154,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -1165,7 +1165,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -952,7 +952,7 @@ export default {
|
|||
background-color: #607d8b!important;
|
||||
border: 1px solid #607d8b!important;
|
||||
}
|
||||
::v-deep.el-progress__text{
|
||||
::v-deep .el-progress__text{
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -1022,7 +1022,7 @@ export default {
|
|||
|
||||
}
|
||||
}
|
||||
::v-deep.el-collapse{
|
||||
::v-deep .el-collapse{
|
||||
border: none;
|
||||
.el-collapse-item{
|
||||
background-color: #000!important;
|
||||
|
|
@ -1043,7 +1043,7 @@ export default {
|
|||
}
|
||||
}
|
||||
.sr-wrapper{
|
||||
::v-deep.el-dialog{
|
||||
::v-deep .el-dialog{
|
||||
background: #fff !important;
|
||||
border: 1px solid #ddd;
|
||||
// color: #ddd;
|
||||
|
|
@ -1051,12 +1051,12 @@ export default {
|
|||
color:#fff;
|
||||
}
|
||||
}
|
||||
::v-deep.sr-dialog-container{
|
||||
::v-deep .sr-dialog-container{
|
||||
margin-top: 50px !important;
|
||||
width:75%;
|
||||
height:80%;
|
||||
}
|
||||
::v-deep.el-dialog__body{
|
||||
::v-deep .el-dialog__body{
|
||||
padding: 10px;
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
|
|
@ -1064,7 +1064,7 @@ export default {
|
|||
position: relative;
|
||||
}
|
||||
.sr-full-dialog-container{
|
||||
::v-deep.is-fullscreen .el-dialog__body{
|
||||
::v-deep .is-fullscreen .el-dialog__body{
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -887,7 +887,7 @@ export default {
|
|||
background-color: #607d8b!important;
|
||||
border: 1px solid #607d8b!important;
|
||||
}
|
||||
::v-deep.el-progress__text{
|
||||
::v-deep .el-progress__text{
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -956,7 +956,7 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
::v-deep.el-collapse{
|
||||
::v-deep .el-collapse{
|
||||
border: none;
|
||||
.el-collapse-item{
|
||||
background-color: #000!important;
|
||||
|
|
@ -977,7 +977,7 @@ export default {
|
|||
}
|
||||
}
|
||||
.sr-wrapper{
|
||||
::v-deep.el-dialog{
|
||||
::v-deep .el-dialog{
|
||||
background: #fff !important;
|
||||
border: 1px solid #ddd;
|
||||
// color: #ddd;
|
||||
|
|
@ -985,12 +985,12 @@ export default {
|
|||
color:#fff;
|
||||
}
|
||||
}
|
||||
::v-deep.sr-dialog-container{
|
||||
::v-deep .sr-dialog-container{
|
||||
margin-top: 50px !important;
|
||||
width:75%;
|
||||
height:80%;
|
||||
}
|
||||
::v-deep.el-dialog__body{
|
||||
::v-deep .el-dialog__body{
|
||||
padding: 10px;
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
|
|
@ -998,7 +998,7 @@ export default {
|
|||
position: relative;
|
||||
}
|
||||
.sr-full-dialog-container{
|
||||
::v-deep.is-fullscreen .el-dialog__body{
|
||||
::v-deep .is-fullscreen .el-dialog__body{
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ export default {
|
|||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.el-message-box__headerbtn {
|
||||
::v-deep .el-message-box__headerbtn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
@ -541,7 +541,7 @@ export default {
|
|||
box-sizing: border-box;
|
||||
background-color: #000;
|
||||
|
||||
::v-deep.el-tabs {
|
||||
::v-deep .el-tabs {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
|
@ -573,11 +573,11 @@ export default {
|
|||
|
||||
}
|
||||
|
||||
::v-deep.hot-keys-label {
|
||||
::v-deep .hot-keys-label {
|
||||
color: #dfdfdf !important;
|
||||
}
|
||||
|
||||
::v-deep.shortcut-key-input span {
|
||||
::v-deep .shortcut-key-input span {
|
||||
color: #dfdfdf !important;
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +588,7 @@ export default {
|
|||
// }
|
||||
|
||||
// }
|
||||
::v-deep.el-dialog {
|
||||
::v-deep .el-dialog {
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #ddd;
|
||||
color: #ddd;
|
||||
|
|
@ -614,13 +614,13 @@ export default {
|
|||
|
||||
}
|
||||
|
||||
::v-deep.dialog-container {
|
||||
::v-deep .dialog-container {
|
||||
margin-top: 50px !important;
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
::v-deep.el-dialog__body {
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 20px;
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
|
@ -636,7 +636,7 @@ export default {
|
|||
}
|
||||
|
||||
.full-dialog-container {
|
||||
::v-deep.is-fullscreen .el-dialog__body {
|
||||
::v-deep .is-fullscreen .el-dialog__body {
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export default {
|
|||
getStudyList(obj) {
|
||||
if (obj) {
|
||||
var studyList = obj.StudyList || []
|
||||
studyList = studyList.filter(i => !i.IsCriticalSequence && (i.Modalities.indexOf('CT') !== -1 || i.Modalities.indexOf('MR') !== -1) && i.Modalities.indexOf('PT') !== -1)
|
||||
studyList = studyList.filter(i => !i.IsCriticalSequence && (i.Modalities.indexOf('CT') !== -1 || i.Modalities.indexOf('MR') !== -1) && (i.Modalities.indexOf('PT') !== -1 || i.Modalities.indexOf('NM') !== -1))
|
||||
if (studyList.length === 0) return
|
||||
this.studyList = studyList
|
||||
}
|
||||
|
|
@ -200,7 +200,7 @@ export default {
|
|||
var series = seriesList.filter(series => series.Modality === 'CT' || series.Modality === 'MR')
|
||||
this.ctSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
|
||||
|
||||
series = seriesList.filter(series => series.Modality === 'PT')
|
||||
series = seriesList.filter(series => series.Modality === 'PT' || series.Modality === 'NM')
|
||||
this.petSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
|
||||
}
|
||||
},
|
||||
|
|
@ -235,12 +235,12 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.series-table {
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
}
|
||||
|
|
@ -251,14 +251,14 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -269,7 +269,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,868 @@
|
|||
<template>
|
||||
<div ref="viewport-volume" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
|
||||
@mouseleave="sliderMouseleave" :style="{ color: '#ddd' }">
|
||||
<div v-if="series && taskInfo" class="left-top-text">
|
||||
<div v-if="taskInfo.IsExistsClinicalData" class="cd-info" :title="$t('trials:reading:button:clinicalData')">
|
||||
<svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon"
|
||||
@click.stop="viewCD(series.TaskInfo.VisitTaskId)" />
|
||||
</div>
|
||||
<h2 v-if="taskInfo.IsReadingShowSubjectInfo && series.TaskInfo" class="subject-info">
|
||||
{{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }}
|
||||
</h2>
|
||||
<div>Series: #{{ series.SeriesNumber }}</div>
|
||||
<div v-if="imageInfo.total">Image: #{{ `${series.SliceIndex + 1}/${imageInfo.total}` }}</div>
|
||||
<div>{{ series.Modality }}</div>
|
||||
</div>
|
||||
<div v-if="series" class="right-top-text">
|
||||
<div>{{ series.Description }}</div>
|
||||
</div>
|
||||
<div v-if="series" class="left-bottom-text">
|
||||
<div v-show="mousePosition.index.length > 0">
|
||||
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
|
||||
</div>
|
||||
<div
|
||||
v-if="(series.Modality === 'CT' || series.Modality === 'DR' || series.Modality === 'CR') && mousePosition.value">
|
||||
HU: {{ mousePosition.value }}
|
||||
</div>
|
||||
<div v-else-if="(series.Modality === 'PT' && mousePosition.value)">
|
||||
SUVbw(g/ml): {{ digitPlaces === -1 ? mousePosition.value.toFixed(3) :
|
||||
mousePosition.value.toFixed(digitPlaces)
|
||||
}}
|
||||
</div>
|
||||
<div v-else-if="mousePosition.value">
|
||||
Density: {{ mousePosition.value }}
|
||||
</div>
|
||||
<div v-show="imageInfo.size">
|
||||
W*H: {{ imageInfo.size }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="series" class="right-bottom-text">
|
||||
<div v-show="imageInfo.location">Location: {{
|
||||
`${Number(imageInfo.location).toFixed(digitPlaces)} mm`
|
||||
}}</div>
|
||||
<div v-show="imageInfo.sliceThickness">Slice Thickness: {{
|
||||
`${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm`
|
||||
}}</div>
|
||||
<div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div>
|
||||
</div>
|
||||
<div class="orientation-top">
|
||||
{{ markers.top }}
|
||||
</div>
|
||||
<div class="orientation-right">
|
||||
{{ markers.right }}
|
||||
</div>
|
||||
|
||||
<div class="orientation-bottom">
|
||||
{{ markers.bottom }}
|
||||
</div>
|
||||
<div class="orientation-left">
|
||||
{{ markers.left }}
|
||||
</div>
|
||||
<div ref="sliderBox" class="right-slider-box" @click.stop="clickSlider($event)">
|
||||
<div :style="{ top: sliderInfo.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
|
||||
@mousedown.stop="sliderMousedown($event)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {
|
||||
metaData,
|
||||
getRenderingEngine,
|
||||
utilities as csUtils,
|
||||
cache
|
||||
} from '@cornerstonejs/core'
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools'
|
||||
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
|
||||
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
||||
import { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
||||
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
|
||||
import { vec3, mat4 } from 'gl-matrix'
|
||||
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
|
||||
export default {
|
||||
name: 'MPRViewport',
|
||||
props: {
|
||||
renderingEngineId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
viewportId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
viewportIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
MPRInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
element: '',
|
||||
series: {},
|
||||
ctSeries: {},
|
||||
petSeries: {},
|
||||
taskInfo: null,
|
||||
sliderInfo: {
|
||||
oldB: null,
|
||||
oldM: null,
|
||||
isMove: false,
|
||||
height: 0
|
||||
},
|
||||
mousePosition: {
|
||||
index: [],
|
||||
value: null,
|
||||
modalityUnit: '',
|
||||
world: []
|
||||
},
|
||||
imageInfo: {
|
||||
zoom: null,
|
||||
size: null,
|
||||
location: null,
|
||||
sliceThickness: null,
|
||||
wwwc: null,
|
||||
total: 0,
|
||||
},
|
||||
digitPlaces: 2,
|
||||
orientationMarkers: [],
|
||||
originalMarkers: [],
|
||||
markers: { top: '', right: '', bottom: '', left: '' },
|
||||
playClipState: false,
|
||||
wwwcIdx: 2,
|
||||
presetName: '',
|
||||
volumeId: null,
|
||||
defaultWindowLevel: {},
|
||||
rotateAngle: 0,
|
||||
rotateBarLeft: 0,
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
|
||||
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
|
||||
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
|
||||
this.$nextTick(() => {
|
||||
this.initViewport()
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
MPRInfo: {
|
||||
handler() {
|
||||
if (!this.series.orientation) return false
|
||||
switch (this.series.orientation) {
|
||||
case 'AXIAL':
|
||||
this.imageInfo.size = `${this.MPRInfo.SAGITTAL.imageNum}*${this.MPRInfo.CORONAL.imageNum}`
|
||||
break;
|
||||
case 'CORONAL':
|
||||
this.imageInfo.size = `${this.MPRInfo.SAGITTAL.imageNum}*${this.MPRInfo.AXIAL.imageNum}`
|
||||
break;
|
||||
case 'SAGITTAL':
|
||||
this.imageInfo.size = `${this.MPRInfo.CORONAL.imageNum}*${this.MPRInfo.AXIAL.imageNum}`
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initViewport() {
|
||||
this.element = this.$refs['viewport-volume']
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
if (renderingEngine) {
|
||||
renderingEngine.resize(true, false)
|
||||
}
|
||||
})
|
||||
this.element.oncontextmenu = (e) => e.preventDefault()
|
||||
// resizeObserver.observe(this.element)
|
||||
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
|
||||
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
|
||||
this.element.addEventListener('wheel', (e) => {
|
||||
// console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL')
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const currentImageIdIndex = viewport.getCurrentImageIdIndex()
|
||||
const totalImages = this.imageInfo.total;
|
||||
|
||||
// 手动实现循环逻辑
|
||||
if (currentImageIdIndex >= totalImages - 1 && e.wheelDeltaY < 0) {
|
||||
// 滚动到最后一张时跳回第一张
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: 0 });
|
||||
} else if (currentImageIdIndex <= 0 && e.wheelDeltaY > 0) {
|
||||
// 滚动到第一张时跳回最后一张
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: totalImages - 1 });
|
||||
}
|
||||
});
|
||||
this.element.addEventListener('CORNERSTONE_TOOLS_MOUSE_MOVE', this.cornerstoneToolsMouseMove)
|
||||
// this.element.addEventListener(cornerstoneTools.Enums.Events.MOUSE_WHEEL, this.handletoolsMouseWheel)
|
||||
this.element.addEventListener('mouseleave', () => {
|
||||
this.mousePosition.index = []
|
||||
this.mousePosition.value = null
|
||||
})
|
||||
document.addEventListener('mouseup', () => {
|
||||
this.sliderMouseup()
|
||||
})
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
this.sliderMousemove(e)
|
||||
|
||||
})
|
||||
},
|
||||
|
||||
determineImagePlane(imageOrientationPatient) {
|
||||
// imageOrientationPatient 是 [rowX, rowY, rowZ, colX, colY, colZ]
|
||||
// 我们只关心行方向向量 (rowX, rowY, rowZ)
|
||||
const [rowX, rowY, rowZ] = imageOrientationPatient;
|
||||
|
||||
// 计算行方向向量与 X, Y, Z 轴的点积(因为轴向量是单位向量,所以点积就是投影长度)
|
||||
const dotX = Math.abs(rowX);
|
||||
const dotY = Math.abs(rowY);
|
||||
const dotZ = Math.abs(rowZ);
|
||||
|
||||
// 找到最大的点积,确定主方向
|
||||
const maxDot = Math.max(dotX, dotY, dotZ);
|
||||
|
||||
// 根据主方向判断解剖面
|
||||
if (maxDot === dotX) {
|
||||
// 行方向接近 X 轴,说明视线方向接近 Y 或 Z 轴。
|
||||
// 更准确的判断是看视线方向(由行和列向量叉乘得到)。
|
||||
// 但一个简化的、在大多数情况下都成立的规则是:
|
||||
// 如果行向量主要在 X-Y 平面 (rowZ 很小),则为 Axial。
|
||||
// 如果行向量主要在 X-Z 平面 (rowY 很小),则为 Sagittal。
|
||||
// 为了简化,我们可以直接根据视线方向(法线)来判断。
|
||||
// 法线方向 = 行向量 × 列向量
|
||||
const [colX, colY, colZ] = imageOrientationPatient.slice(3);
|
||||
const normalX = rowY * colZ - rowZ * colY;
|
||||
const normalY = rowZ * colX - rowX * colZ;
|
||||
const normalZ = rowX * colY - rowY * colX;
|
||||
|
||||
const absNormalX = Math.abs(normalX);
|
||||
const absNormalY = Math.abs(normalY);
|
||||
const absNormalZ = Math.abs(normalZ);
|
||||
|
||||
const maxNormal = Math.max(absNormalX, absNormalY, absNormalZ);
|
||||
|
||||
if (maxNormal === absNormalZ) {
|
||||
return 'AXIAL';
|
||||
} else if (maxNormal === absNormalY) {
|
||||
return 'SAGITTAL';
|
||||
} else if (maxNormal === absNormalX) {
|
||||
return 'CORONAL';
|
||||
}
|
||||
|
||||
} else if (maxDot === dotY) {
|
||||
// 行方向接近 Y 轴,通常是 Coronal 面。
|
||||
return 'SAGITTAL';
|
||||
} else if (maxDot === dotZ) {
|
||||
// 行方向接近 Z 轴,通常是 Sagittal 面。
|
||||
return 'CORONAL';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
},
|
||||
stackNewImage(e) {
|
||||
const { detail } = e
|
||||
this.series.SliceIndex = detail.imageIndex
|
||||
this.sliderInfo.height = detail.imageIndex * 100 / detail.numberOfSlices
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const zoom = viewport.getZoom()
|
||||
this.imageInfo.zoom = zoom.toFixed(4)
|
||||
let imageIds = viewport.getImageIds(this.volumeId)
|
||||
let imageId = imageIds[0]
|
||||
let volume = cache.getVolume(this.volumeId)
|
||||
let spacing = volume ? volume.spacing : []
|
||||
// if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId()
|
||||
if (imageId && volume) {
|
||||
this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices })
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
|
||||
let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient)
|
||||
this.imageInfo.location = type === this.series.orientation ? imagePlaneModule.sliceLocation : ''
|
||||
this.imageInfo.sliceThickness = type === this.series.orientation ? spacing[2] : spacing[0]
|
||||
this.imageInfo.total = detail.numberOfSlices
|
||||
this.getOrientationMarker()
|
||||
let properties = viewport.getProperties(this.volumeId)
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const windowWidth = upper - lower
|
||||
const windowCenter = (upper + lower) / 2
|
||||
this.defaultWindowLevel.windowWidth = windowWidth
|
||||
this.defaultWindowLevel.windowCenter = windowCenter
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
}
|
||||
}
|
||||
if (volume) {
|
||||
const toolGroupId = 'share-viewport-volume'
|
||||
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
|
||||
toolGroup.setToolEnabled('ScaleOverlay')
|
||||
}
|
||||
// const toolGroupId = this.viewportId
|
||||
},
|
||||
setFullScreen(index) {
|
||||
this.series.SliceIndex = index
|
||||
setTimeout(() => {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportId
|
||||
)
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: index })
|
||||
viewport.render()
|
||||
})
|
||||
},
|
||||
voiModified(e) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
let properties = viewport.getProperties()
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
|
||||
lower,
|
||||
upper
|
||||
)
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
this.$emit('upperRangeChange', Math.round(windowWidth))
|
||||
}
|
||||
},
|
||||
getOrientationMarker() {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const { viewUp, viewPlaneNormal } = viewport.getCamera()
|
||||
|
||||
const viewRight = vec3.create()
|
||||
vec3.cross(viewRight, viewUp, viewPlaneNormal)
|
||||
|
||||
const columnCosines = [-viewUp[0], -viewUp[1], -viewUp[2]]
|
||||
const rowCosines = viewRight
|
||||
const rowString = cornerstoneTools.utilities.orientation.getOrientationStringLPS(rowCosines)
|
||||
const columnString = cornerstoneTools.utilities.orientation.getOrientationStringLPS(columnCosines)
|
||||
const oppositeRowString = cornerstoneTools.utilities.orientation.invertOrientationStringLPS(rowString)
|
||||
const oppositeColumnString = cornerstoneTools.utilities.orientation.invertOrientationStringLPS(columnString)
|
||||
this.markers.top = oppositeColumnString
|
||||
this.markers.right = rowString
|
||||
this.markers.bottom = columnString
|
||||
this.markers.left = oppositeRowString
|
||||
this.orientationMarkers = [oppositeColumnString, rowString, columnString, oppositeRowString]
|
||||
if (this.originalMarkers.length === 0) {
|
||||
this.originalMarkers = [...this.orientationMarkers]
|
||||
}
|
||||
},
|
||||
setMarkers() {
|
||||
const markers = [...this.orientationMarkers]
|
||||
for (const key in this.markers) {
|
||||
const v = markers.shift(0)
|
||||
this.markers[key] = v
|
||||
}
|
||||
},
|
||||
resetOrientationMarkers() {
|
||||
if (this.originalMarkers.length > 0) {
|
||||
this.orientationMarkers = [...this.originalMarkers]
|
||||
this.setMarkers()
|
||||
}
|
||||
},
|
||||
rotateOrientationMarkers(type) {
|
||||
if (this.orientationMarkers.length > 0) {
|
||||
if (type === 1) {
|
||||
this.resetOrientationMarkers()
|
||||
return
|
||||
}
|
||||
const markers = [...this.orientationMarkers]
|
||||
if (type === 2) {
|
||||
// 垂直翻转
|
||||
this.orientationMarkers[0] = markers[2]
|
||||
this.orientationMarkers[2] = markers[0]
|
||||
} else if (type === 3) {
|
||||
// 水平翻转
|
||||
this.orientationMarkers[1] = markers[3]
|
||||
this.orientationMarkers[3] = markers[1]
|
||||
} else if (type === 4) {
|
||||
// 左转90度
|
||||
this.orientationMarkers = markers.slice(1, 4).concat(markers[0])
|
||||
} else if (type === 5) {
|
||||
// 右转90度
|
||||
this.orientationMarkers = [markers[3]].concat(markers.slice(0, 3))
|
||||
}
|
||||
this.setMarkers()
|
||||
}
|
||||
},
|
||||
toggleClipPlay(isPlay, framesPerSecond) {
|
||||
this.playClipState = isPlay
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
|
||||
if (isPlay) {
|
||||
cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true })
|
||||
} else {
|
||||
cornerstoneTools.utilities.cine.stopClip(viewport.element)
|
||||
}
|
||||
},
|
||||
scrollPage(type) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const currentImageIdIndex = viewport.getCurrentImageIdIndex()
|
||||
const numImages = viewport.getImageIds().length
|
||||
let newImageIdIndex = null
|
||||
if (type === 0) {
|
||||
newImageIdIndex = 0
|
||||
} else if (type === -1) {
|
||||
newImageIdIndex = currentImageIdIndex === 0 ? currentImageIdIndex : currentImageIdIndex - 1
|
||||
} else if (type === 1) {
|
||||
newImageIdIndex = currentImageIdIndex === numImages - 1 ? currentImageIdIndex : currentImageIdIndex + 1
|
||||
} else if (type === 99999) {
|
||||
newImageIdIndex = numImages - 1
|
||||
}
|
||||
// viewport.setImageIdIndex(newImageIdIndex)
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: newImageIdIndex })
|
||||
cornerstoneTools.utilities.cine.stopClip(viewport.element)
|
||||
},
|
||||
setZoom(ratio) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
|
||||
const zoom = viewport.getZoom()
|
||||
if (ratio > 0) {
|
||||
viewport.setZoom(zoom * 1.05)
|
||||
} else {
|
||||
viewport.setZoom(zoom / 1.05)
|
||||
}
|
||||
viewport.render()
|
||||
},
|
||||
resize(forceFitToWindow) {
|
||||
console.log('resize: ', forceFitToWindow)
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (!forceFitToWindow) {
|
||||
viewport.setZoom(0.5)
|
||||
viewport.render()
|
||||
} else {
|
||||
viewport.setZoom(1)
|
||||
viewport.render()
|
||||
}
|
||||
},
|
||||
voiChange(v) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const voiRange = { lower: 0, upper: v }
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (!viewport) return
|
||||
let volumeId = this.volumeId
|
||||
const viewportsContainingVolumeUID = csUtils.getViewportsWithVolumeId(
|
||||
volumeId,
|
||||
viewport.renderingEngineId
|
||||
)
|
||||
|
||||
viewport.setProperties({ voiRange }, volumeId)
|
||||
viewportsContainingVolumeUID.forEach((vp) => {
|
||||
vp.render()
|
||||
// this.$refs[vp.id].voiModified()
|
||||
this.voiModified()
|
||||
})
|
||||
},
|
||||
setPreset(presetName) {
|
||||
this.presetName = presetName
|
||||
},
|
||||
async createImageIdsAndCacheMetaData(obj) {
|
||||
this.loading = true
|
||||
try {
|
||||
return await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async setSeriesInfo(obj, isLocate = false) {
|
||||
try {
|
||||
let data = obj
|
||||
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate && !data.isLocation) {
|
||||
data.SliceIndex = this.series.SliceIndex
|
||||
}
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
|
||||
this.series = {}
|
||||
this.volumeId = data.SeriesInstanceUid
|
||||
this.series = { ...data }
|
||||
viewport
|
||||
.setVolumes([{
|
||||
volumeId: this.volumeId, callback: (r) => {
|
||||
if (this.series.Modality === 'PT') {
|
||||
setPetColorMapTransferFunctionForVolumeActor(r, true)
|
||||
} else {
|
||||
let volume = cache.getVolume(this.volumeId)
|
||||
const voi = metaData.get('voiLutModule', volume._imageIds[Math.ceil((volume._imageIds.length - 1) / 2)])
|
||||
setCtMappingRange(voi.windowWidth[0], voi.windowCenter[0])
|
||||
setCtTransferFunctionForVolumeActor(r)
|
||||
}
|
||||
console.log("渲染成功")
|
||||
DicomEvent.$emit("isloaded", { isChange: false })
|
||||
}
|
||||
}]).then(r => {
|
||||
if (data.isLocation || !this.imageInfo.zoom) {
|
||||
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
|
||||
}
|
||||
})
|
||||
viewport.render()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
},
|
||||
cornerstoneToolsMouseMove(e) {
|
||||
const { currentPoints } = e.detail
|
||||
const worldPoint = currentPoints.world
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
let referencedImageId = viewport.getCurrentImageId()
|
||||
const data = viewport.getImageData()
|
||||
if (!data) return
|
||||
const { dimensions, imageData, metadata, voxelManager } = data
|
||||
const index = imageData.worldToIndex(worldPoint)
|
||||
index[0] = Math.floor(index[0])
|
||||
index[1] = Math.floor(index[1])
|
||||
index[2] = Math.floor(index[2])
|
||||
this.mousePosition.index = index
|
||||
const modality = metadata.Modality
|
||||
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
|
||||
ijk = vec3.round(ijk, ijk);
|
||||
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
|
||||
this.isHandleOutsideImage = false
|
||||
let value = voxelManager.getAtIJKPoint(ijk)
|
||||
ijk[2] = viewport.getCurrentImageIdIndex()
|
||||
let modalityUnit
|
||||
if (modality === 'US') {
|
||||
const calibratedResults = cornerstoneTools.utilities.getCalibratedProbeUnitsAndValue(image, [ijk])
|
||||
const hasEnhancedRegionValues = calibratedResults.values.every(
|
||||
(value) => value !== null
|
||||
)
|
||||
value = (hasEnhancedRegionValues ? calibratedResults.values : value)
|
||||
modalityUnit = hasEnhancedRegionValues
|
||||
? calibratedResults.units
|
||||
: 'raw';
|
||||
} else {
|
||||
const scalingModule = referencedImageId && metaData.get('scalingModule', referencedImageId)
|
||||
const isSuvScaled = typeof scalingModule?.suvbw === 'number'
|
||||
if (scalingModule && scalingModule.suvbw) {
|
||||
const pixelUnitsOptions = {
|
||||
isPreScaled: cornerstoneTools.utilities.viewport.isViewportPreScaled(viewport, referencedImageId),
|
||||
isSuvScaled: isSuvScaled,
|
||||
}
|
||||
modalityUnit = cornerstoneTools.utilities.getPixelValueUnits(
|
||||
modality,
|
||||
referencedImageId,
|
||||
pixelUnitsOptions
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
this.mousePosition.value = value
|
||||
this.mousePosition.modalityUnit = modalityUnit
|
||||
}
|
||||
},
|
||||
toggleTask(evt, visitTaskNum, i) {
|
||||
this.$emit('activeViewport', this.viewportIndex)
|
||||
const num = visitTaskNum + i
|
||||
if (num >= 0 && num <= this.taskInfo.VisitNum) {
|
||||
this.$emit('toggleTaskByViewport', { series: this.series, visitTaskNum: num })
|
||||
}
|
||||
|
||||
evt.stopImmediatePropagation()
|
||||
evt.stopPropagation()
|
||||
evt.preventDefault()
|
||||
},
|
||||
viewCD(taskId) {
|
||||
this.$emit('previewCD', taskId)
|
||||
},
|
||||
setWwwcIdx(idx) {
|
||||
this.wwwcIdx = idx
|
||||
},
|
||||
clickSlider(e) {
|
||||
const height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
|
||||
this.sliderInfo.height = height
|
||||
let sliceIdx = Math.trunc(this.imageInfo.total * height / 100)
|
||||
sliceIdx = sliceIdx >= this.imageInfo.total ? this.imageInfo.total : sliceIdx < 0 ? 0 : sliceIdx
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportId
|
||||
)
|
||||
// viewport.setImageIdIndex(sliceIdx)
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIdx })
|
||||
viewport.render()
|
||||
},
|
||||
sliderMouseup(e) {
|
||||
this.sliderInfo.isMove = false
|
||||
this.$emit('contentMouseup', e)
|
||||
},
|
||||
sliderMousedown(e) {
|
||||
const boxHeight = this.$refs['sliderBox'].clientHeight
|
||||
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
|
||||
this.sliderInfo.oldM = e.clientY
|
||||
this.sliderInfo.isMove = true
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
sliderMousemove(e) {
|
||||
if (!this.sliderInfo.isMove) return
|
||||
const delta = this.sliderInfo.oldB - (this.sliderInfo.oldM - e.clientY)
|
||||
const boxHeight = this.$refs['sliderBox'].clientHeight
|
||||
if (delta < 0) return
|
||||
if (delta > boxHeight) return
|
||||
const height = delta * 100 / boxHeight
|
||||
let sliceIdx = Math.trunc(this.imageInfo.total * height / 100)
|
||||
sliceIdx = sliceIdx >= this.imageInfo.total ? this.imageInfo.total - 1 : sliceIdx < 0 ? 0 : sliceIdx
|
||||
this.sliderInfo.height = height
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportId
|
||||
)
|
||||
// viewport.setImageIdIndex(sliceIdx)
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIdx })
|
||||
viewport.render()
|
||||
},
|
||||
sliderMouseleave(e) {
|
||||
if (!this.sliderInfo.isMove) return
|
||||
this.sliderInfo.isMove = false
|
||||
},
|
||||
rotate(angle) {
|
||||
let renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
let viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const camera = viewport.getCamera()
|
||||
const { viewUp, position, focalPoint } = camera
|
||||
const [cx, cy, cz] = focalPoint
|
||||
const [ax, ay, az] = [0, 0, 1]
|
||||
const newPosition = [0, 0, 0]
|
||||
const newFocalPoint = [0, 0, 0]
|
||||
const newViewUp = [0, 0, 0]
|
||||
|
||||
const transform = mat4.identity(new Float32Array(16))
|
||||
mat4.translate(transform, transform, [cx, cy, cz])
|
||||
mat4.rotate(transform, transform, angle, [ax, ay, az])
|
||||
mat4.translate(transform, transform, [-cx, -cy, -cz])
|
||||
vec3.transformMat4(newPosition, position, transform)
|
||||
vec3.transformMat4(newFocalPoint, focalPoint, transform)
|
||||
|
||||
mat4.identity(transform)
|
||||
mat4.rotate(transform, transform, angle, [ax, ay, az])
|
||||
vec3.transformMat4(newViewUp, viewUp, transform)
|
||||
|
||||
viewport.setCamera({
|
||||
position: newPosition,
|
||||
viewUp: newViewUp,
|
||||
focalPoint: newFocalPoint
|
||||
})
|
||||
|
||||
viewport.render()
|
||||
},
|
||||
clickRotate(e) {
|
||||
// console.log('clickRotate')
|
||||
const container = document.getElementById('rotateBar')
|
||||
const containerWidth = container.offsetWidth
|
||||
const slider = document.getElementById('rotateSlider')
|
||||
const sliderWidth = slider.offsetWidth
|
||||
const x = Math.trunc(e.offsetX)
|
||||
const deltaX = x - this.rotateBarLeft
|
||||
const angle = Math.sin((deltaX * (360 / (containerWidth - sliderWidth))) * Math.PI / 180)
|
||||
this.rotate(angle)
|
||||
this.rotateBarLeft = x
|
||||
},
|
||||
preventDefault(e) {
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
NSTip() {
|
||||
return `NS: ${this.$store.state.trials.downloadTip}`
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.viewport-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
cursor: default !important;
|
||||
|
||||
.left-top-text {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
// color: #ddd;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
|
||||
.cd-info {
|
||||
// color: #ddd;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.subject-info {
|
||||
color: #f44336;
|
||||
padding: 5px 0px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.top-center-tool {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 5px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
|
||||
.toggle-visit-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.arrw_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #3f3f3f;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
.arrow_text {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
background-color: #00000057;
|
||||
color: #fff;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-top-text {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
// color: #ddd;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.left-bottom-text {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
bottom: 5px;
|
||||
// color: #ddd;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.right-bottom-text {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
// color: #ddd;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.right-slider-box {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
height: calc(100% - 140px);
|
||||
transform: translateY(-50%);
|
||||
top: calc(50% - 30px);
|
||||
width: 10px;
|
||||
background: #333;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.right-slider-box:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.slider {
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: #9e9e9e;
|
||||
cursor: move
|
||||
}
|
||||
|
||||
.orientation-top {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30px;
|
||||
color: #f44336;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.orientation-bottom {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 15px;
|
||||
color: #f44336;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.orientation-left {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 15px;
|
||||
color: #f44336;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.orientation-right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 15px;
|
||||
color: #f44336;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.color_bar {
|
||||
position: absolute;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: right;
|
||||
left: -150px;
|
||||
top: 30%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.rotate_slider_box {
|
||||
position: absolute;
|
||||
width: 380px;
|
||||
height: 10px;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #333;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
|
||||
.box {
|
||||
z-index: 10;
|
||||
background: #9e9e9e;
|
||||
height: 100%;
|
||||
width: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: move
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
<template>
|
||||
<div ref="viewport-fusion" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
|
||||
@mouseleave="sliderMouseleave" :style="{ color: series.Modality === 'PT' || isMip ? '#666' : '#ddd' }">
|
||||
@mouseleave="sliderMouseleave"
|
||||
:style="{ color: series.Modality === 'PT' || series.Modality === 'NM' || isMip ? '#666' : '#ddd' }">
|
||||
<div v-if="isFusion" class="opacity-slider-wrapper" @mousedown.stop @mousemove.stop @mouseup.stop @wheel.stop>
|
||||
<div class="slider-title">{{ Math.round(fusionOpacity * 100) }}%</div>
|
||||
<input type="range" min="0" max="1" step="0.01" v-model.number="fusionOpacity" @input="applyFusionOpacity"
|
||||
class="opacity-slider" />
|
||||
</div>
|
||||
<div v-if="series && taskInfo" class="left-top-text">
|
||||
<div v-if="taskInfo.IsExistsClinicalData && !isMip && !isFusion" class="cd-info"
|
||||
:title="$t('trials:reading:button:clinicalData')">
|
||||
|
|
@ -12,32 +18,18 @@
|
|||
{{ `${series.TaskInfo.SubjectCode} ${series.TaskInfo.TaskBlindName} ` }}
|
||||
</h2>
|
||||
<div v-if="!isMip && !isFusion">Series: #{{ series.SeriesNumber }}</div>
|
||||
<div v-if="series.Stack && !isMip">Image: #{{ `${series.SliceIndex + 1}/${series.Stack.length}` }}</div>
|
||||
<div v-if="series.Stack && !isMip">Image: #{{ `${series.SliceIndex + 1}/${imageInfo.total || series.Stack.length}` }}</div>
|
||||
<div v-if="!isMip && !isFusion">{{ series.Modality }}</div>
|
||||
<div v-if="isFusion">{{ series.Modality }} / {{ ctSeries.Modality }}</div>
|
||||
<div v-if="isMip">MIP</div>
|
||||
</div>
|
||||
<!-- <div v-if="series && taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && series.TaskInfo && !isMip && !isFusion"
|
||||
class="top-center-tool">
|
||||
<div class="toggle-visit-container">
|
||||
<div class="arrw_icon"
|
||||
:style="{ cursor: series.TaskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: series.TaskInfo.VisitTaskNum !== 0 ? '#fff' : '#6b6b6b' }"
|
||||
@click.stop.prevent="toggleTask($event, series.TaskInfo.VisitTaskNum, -1)"
|
||||
@dblclick.stop="preventDefault($event)">
|
||||
<i class="el-icon-caret-left" />
|
||||
</div>
|
||||
<div class="arrow_text">
|
||||
{{ series.TaskInfo.TaskBlindName }}
|
||||
</div>
|
||||
<div class="arrw_icon"
|
||||
:style="{ cursor: series.TaskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: series.TaskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff' : '#6b6b6b' }"
|
||||
@click.stop.prevent="toggleTask($event, series.TaskInfo.VisitTaskNum, 1)"
|
||||
@dblclick.stop="preventDefault($event)">
|
||||
<i class="el-icon-caret-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div v-if="series && !isMip && !isFusion" class="right-top-text">
|
||||
<div>{{ series.Description }}</div>
|
||||
</div>
|
||||
<!-- <div v-if="isFusion && !isMip" class="fusion-order-toggle" @mousedown.stop @mouseup.stop
|
||||
@click.stop="toggleFusionRenderOrder">
|
||||
{{ fusionCtOnTop ? `${ctSeries.Modality}/${series.Modality}` : `${series.Modality}/${ctSeries.Modality}` }}
|
||||
</div> -->
|
||||
<div v-if="series" class="left-bottom-text">
|
||||
<div v-show="mousePosition.index.length > 0 && !isMip">
|
||||
Pos: {{ mousePosition.index[0] }}, {{ mousePosition.index[1] }}, {{ mousePosition.index[2] }}
|
||||
|
|
@ -104,10 +96,16 @@ import {
|
|||
import * as cornerstoneTools from '@cornerstonejs/tools'
|
||||
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
|
||||
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'
|
||||
|
||||
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
|
||||
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
||||
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
|
||||
const { BlendModes, OrientationAxis } = Enums;
|
||||
import {
|
||||
setMipTransferFunctionForVolumeActor,
|
||||
setPetTransferFunctionForVolumeActor
|
||||
} from './helpers/index.js'
|
||||
import setNmFusionColorMapTransferFunctionForVolumeActor from './helpers/setNmFusionColorMapTransferFunctionForVolumeActor'
|
||||
const { BlendModes, OrientationAxis } = Enums;
|
||||
const { getColormap } = csUtils.colormap;
|
||||
import { vec3, mat4 } from 'gl-matrix'
|
||||
export default {
|
||||
|
|
@ -134,6 +132,7 @@ export default {
|
|||
petSeries: {},
|
||||
isFusion: false,
|
||||
isMip: false,
|
||||
fusionCtOnTop: false,
|
||||
taskInfo: null,
|
||||
sliderInfo: {
|
||||
oldB: null,
|
||||
|
|
@ -152,7 +151,8 @@ export default {
|
|||
size: null,
|
||||
location: null,
|
||||
sliceThickness: null,
|
||||
wwwc: null
|
||||
wwwc: null,
|
||||
total: null
|
||||
},
|
||||
digitPlaces: 2,
|
||||
orientationMarkers: [],
|
||||
|
|
@ -172,7 +172,11 @@ export default {
|
|||
isMove: false
|
||||
},
|
||||
ptVolumeId: null,
|
||||
loading: false
|
||||
loading: false,
|
||||
Colorbar: null,
|
||||
fusionOpacity: 0.95,
|
||||
topFusionVolumeActor: null,
|
||||
currentVoiUpper: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -220,8 +224,12 @@ export default {
|
|||
},
|
||||
stackNewImage(e) {
|
||||
const { detail } = e
|
||||
this.series.SliceIndex = detail.imageIndex
|
||||
this.sliderInfo.height = detail.imageIndex * 100 / detail.numberOfSlices
|
||||
if (this.series) {
|
||||
this.series.SliceIndex = detail.imageIndex
|
||||
}
|
||||
this.imageInfo.total = detail.numberOfSlices
|
||||
const maxIndex = detail.numberOfSlices > 1 ? detail.numberOfSlices - 1 : 1
|
||||
this.sliderInfo.height = detail.imageIndex * 100 / maxIndex
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const zoom = viewport.getZoom()
|
||||
|
|
@ -267,16 +275,29 @@ export default {
|
|||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
let properties = viewport.getProperties()
|
||||
if (this.isFusion) {
|
||||
properties = viewport.getProperties(this.volumeId)
|
||||
properties = viewport.getProperties(this.ptVolumeId || this.volumeId)
|
||||
}
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
if ((!upper || upper < 1) && this.currentVoiUpper >= 1) {
|
||||
upper = this.currentVoiUpper
|
||||
} else if (upper) {
|
||||
this.currentVoiUpper = upper
|
||||
}
|
||||
|
||||
if (!upper || upper < 1) return
|
||||
|
||||
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
|
||||
lower,
|
||||
upper
|
||||
)
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
this.$emit('upperRangeChange', Math.round(windowWidth))
|
||||
if (this.series.Modality === 'PT' || this.series.Modality === 'NM' || this.isFusion) {
|
||||
this.$emit('upperRangeChange', Math.round(upper))
|
||||
}
|
||||
if (this.isFusion && !this.fusionCtOnTop && this.topFusionVolumeActor) {
|
||||
this.applyFusionOpacity()
|
||||
}
|
||||
}
|
||||
},
|
||||
getOrientationMarker() {
|
||||
|
|
@ -397,6 +418,8 @@ export default {
|
|||
voiChange(v) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const voiRange = { lower: 0, upper: v }
|
||||
this.currentVoiUpper = v
|
||||
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (!viewport) return
|
||||
let volumeId = this.isFusion ? this.ptVolumeId : this.volumeId
|
||||
|
|
@ -406,6 +429,11 @@ export default {
|
|||
)
|
||||
|
||||
viewport.setProperties({ voiRange }, volumeId)
|
||||
|
||||
// if (this.isFusion && !this.fusionCtOnTop && this.topFusionVolumeActor) {
|
||||
// this.applyFusionOpacity()
|
||||
// }
|
||||
|
||||
viewportsContainingVolumeUID.forEach((vp) => {
|
||||
vp.render()
|
||||
// this.$refs[vp.id].voiModified()
|
||||
|
|
@ -445,6 +473,32 @@ export default {
|
|||
ctx.fillStyle = gradient
|
||||
ctx.fillRect(0, 0, rectWidth, rectHeight)
|
||||
},
|
||||
opacityChange(opacity) {
|
||||
this.fusionOpacity = opacity
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (!viewport) return
|
||||
|
||||
if (this.isFusion) {
|
||||
// const ctVolumeId = this.ctSeries?.SeriesInstanceUid
|
||||
const ptVolumeId = this.ptVolumeId
|
||||
viewport.setProperties({ colormap: { opacity: Number(opacity) } }, ptVolumeId)
|
||||
// const topVolumeId = this.fusionCtOnTop ? ctVolumeId : ptVolumeId
|
||||
// const bottomVolumeId = this.fusionCtOnTop ? ptVolumeId : ctVolumeId
|
||||
// const topOpacity = Number(opacity)
|
||||
// const bottomOpacity = 1 - topOpacity
|
||||
// if (bottomVolumeId) {
|
||||
// viewport.setProperties({ colormap: { opacity: bottomOpacity } }, bottomVolumeId)
|
||||
// }
|
||||
// if (topVolumeId) {
|
||||
// viewport.setProperties({ colormap: { opacity: topOpacity } }, topVolumeId)
|
||||
// }
|
||||
}
|
||||
viewport.render()
|
||||
},
|
||||
applyFusionOpacity() {
|
||||
this.opacityChange(this.fusionOpacity)
|
||||
},
|
||||
setPreset(presetName) {
|
||||
this.presetName = presetName
|
||||
},
|
||||
|
|
@ -470,11 +524,109 @@ export default {
|
|||
},
|
||||
async createImageIdsAndCacheMetaData(obj) {
|
||||
this.loading = true
|
||||
await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
try {
|
||||
return await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
getFusionVolumes() {
|
||||
const ctVolumeId = this.ctSeries?.SeriesInstanceUid
|
||||
const ptFusionVolumeId = this.ptVolumeId
|
||||
if (!ctVolumeId || !ptFusionVolumeId) {
|
||||
return []
|
||||
}
|
||||
|
||||
const ctEntry = {
|
||||
volumeId: ctVolumeId,
|
||||
callback: (r) => {
|
||||
setCtTransferFunctionForVolumeActor({ ...r, volumeId: ctVolumeId })
|
||||
if (this.fusionCtOnTop) {
|
||||
this.topFusionVolumeActor = r.volumeActor
|
||||
}
|
||||
this.applyFusionOpacity()
|
||||
console.log("融合ct渲染成功")
|
||||
}
|
||||
}
|
||||
|
||||
const ptFusionEntry = {
|
||||
volumeId: ptFusionVolumeId,
|
||||
callback: (r) => {
|
||||
if (this.series.Modality === 'NM') {
|
||||
setNmFusionColorMapTransferFunctionForVolumeActor({
|
||||
...r,
|
||||
volumeId: ptFusionVolumeId,
|
||||
})
|
||||
} else {
|
||||
setPetColorMapTransferFunctionForVolumeActor({
|
||||
...r,
|
||||
volumeId: ptFusionVolumeId,
|
||||
})
|
||||
}
|
||||
if (!this.fusionCtOnTop) {
|
||||
this.topFusionVolumeActor = r.volumeActor
|
||||
}
|
||||
this.applyFusionOpacity()
|
||||
console.log("融合pet渲染成功")
|
||||
}
|
||||
}
|
||||
|
||||
const volumes = []
|
||||
// if (this.series.Modality !== 'NM') {
|
||||
// volumes.push({
|
||||
// volumeId: this.volumeId,
|
||||
// callback: (r) => {
|
||||
// console.log("融合pet渲染成功");
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
volumes.push({
|
||||
volumeId: this.volumeId,
|
||||
callback: (r) => {
|
||||
console.log("融合pet渲染成功");
|
||||
}
|
||||
})
|
||||
this.loading = false
|
||||
|
||||
if (this.fusionCtOnTop) {
|
||||
volumes.push(ptFusionEntry, ctEntry)
|
||||
} else {
|
||||
volumes.push(ctEntry, ptFusionEntry)
|
||||
}
|
||||
|
||||
return volumes
|
||||
},
|
||||
async applyFusionRenderOrder() {
|
||||
if (!this.isFusion) return
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine?.getViewport?.(this.viewportId)
|
||||
if (!viewport) return
|
||||
|
||||
const volumes = this.getFusionVolumes()
|
||||
if (!volumes.length) return
|
||||
|
||||
const camera = viewport.getCamera?.()
|
||||
|
||||
const savedVoiUpper = this.currentVoiUpper
|
||||
|
||||
await viewport.setVolumes(volumes)
|
||||
if (camera) {
|
||||
viewport.setCamera(camera)
|
||||
}
|
||||
|
||||
if (savedVoiUpper) {
|
||||
viewport.setProperties({ voiRange: { lower: 0, upper: savedVoiUpper } }, this.ptVolumeId)
|
||||
}
|
||||
|
||||
viewport.render()
|
||||
},
|
||||
toggleFusionRenderOrder() {
|
||||
if (!this.isFusion) return
|
||||
this.fusionCtOnTop = !this.fusionCtOnTop
|
||||
this.applyFusionRenderOrder()
|
||||
this.$emit('upperRangeChange', Math.round(this.currentVoiUpper))
|
||||
},
|
||||
async setSeriesInfo(obj, isLocate = false, option = {}) {
|
||||
try {
|
||||
|
|
@ -482,17 +634,18 @@ export default {
|
|||
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate) {
|
||||
data.SliceIndex = this.series.SliceIndex
|
||||
}
|
||||
// this.series = { ...data }
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
|
||||
this.volumeId = data.SeriesInstanceUid
|
||||
this.ptVolumeId = null
|
||||
this.series = {}
|
||||
this.topFusionVolumeActor = null
|
||||
let { isFusion, isMip, colorMap } = option
|
||||
this.isFusion = isFusion;
|
||||
this.isMip = isMip;
|
||||
if (this.isFusion) {
|
||||
this.fusionCtOnTop = false
|
||||
this.$nextTick(() => {
|
||||
this.renderColorBar(this.presetName)
|
||||
})
|
||||
|
|
@ -500,32 +653,10 @@ export default {
|
|||
let { ct, data } = obj
|
||||
this.series = { ...data }
|
||||
this.ctSeries = { ...ct }
|
||||
this.petSeries = { ...data }
|
||||
await viewport
|
||||
.setVolumes([
|
||||
{
|
||||
volumeId: this.volumeId, callback: (r) => {
|
||||
setPetColorMapTransferFunctionForVolumeActor(r)
|
||||
console.log("融合pet渲染成功");
|
||||
}
|
||||
},
|
||||
{
|
||||
volumeId: ct.SeriesInstanceUid, callback: (r) => {
|
||||
setCtTransferFunctionForVolumeActor(r)
|
||||
console.log("融合ct渲染成功")
|
||||
}
|
||||
},
|
||||
{
|
||||
volumeId: this.ptVolumeId, callback: (r) => {
|
||||
setPetColorMapTransferFunctionForVolumeActor(r)
|
||||
console.log("融合pet渲染成功");
|
||||
}
|
||||
},
|
||||
]).then(res => {
|
||||
if (colorMap) {
|
||||
this.setColorMap(this.presetName)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
await viewport.setVolumes(this.getFusionVolumes())
|
||||
} else {
|
||||
this.series = { ...data }
|
||||
if (this.isMip) {
|
||||
|
|
@ -540,7 +671,12 @@ export default {
|
|||
.setVolumes([{
|
||||
volumeId: this.volumeId,
|
||||
callback: (r) => {
|
||||
setPetColorMapTransferFunctionForVolumeActor(r)
|
||||
if (this.series.Modality === 'NM') {
|
||||
setMipTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
||||
} else {
|
||||
setPetTransferFunctionForVolumeActor(r)
|
||||
}
|
||||
// setPetColorMapTransferFunctionForVolumeActor(r)
|
||||
console.log("mip渲染成功")
|
||||
},
|
||||
slabThickness,
|
||||
|
|
@ -548,51 +684,80 @@ export default {
|
|||
defaultOptions: {
|
||||
orientation: OrientationAxis.CORONAL
|
||||
}
|
||||
}]).then(res => {
|
||||
if (colorMap) {
|
||||
this.setColorMap(this.presetName)
|
||||
}
|
||||
if (isLocate) {
|
||||
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
|
||||
}
|
||||
})
|
||||
}])
|
||||
} else {
|
||||
viewport
|
||||
.setVolumes([{
|
||||
volumeId: this.volumeId, callback: (r) => {
|
||||
if (this.series.Modality === 'PT') {
|
||||
setPetColorMapTransferFunctionForVolumeActor(r, true)
|
||||
if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
|
||||
// setPetColorMapTransferFunctionForVolumeActor(r, true)
|
||||
setPetTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
||||
} else {
|
||||
setCtTransferFunctionForVolumeActor(r)
|
||||
setCtTransferFunctionForVolumeActor({ ...r, volumeId: this.volumeId })
|
||||
}
|
||||
console.log("渲染成功")
|
||||
}
|
||||
}]).then(res => {
|
||||
if (colorMap) {
|
||||
this.setColorMap(this.presetName)
|
||||
}
|
||||
})
|
||||
}])
|
||||
}
|
||||
|
||||
}
|
||||
viewport.render()
|
||||
this.voiChange(this.currentVoiUpper)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
},
|
||||
cornerstoneToolsMouseMove(e) {
|
||||
const { currentPoints } = e.detail
|
||||
const worldPoint = currentPoints.world
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const imageData = viewport.getImageData()
|
||||
if (!imageData) return
|
||||
const index = imageData.imageData.worldToIndex(worldPoint)
|
||||
index[0] = Math.floor(index[0])
|
||||
index[1] = Math.floor(index[1])
|
||||
index[2] = Math.floor(index[2])
|
||||
this.mousePosition.index = index
|
||||
try {
|
||||
const { currentPoints } = e.detail
|
||||
const worldPoint = currentPoints.world
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
let referencedImageId = viewport.getCurrentImageId()
|
||||
const data = viewport.getImageData()
|
||||
if (!data || !referencedImageId) return
|
||||
const { dimensions, imageData, metadata, voxelManager } = data
|
||||
const index = imageData.worldToIndex(worldPoint)
|
||||
index[0] = Math.floor(index[0])
|
||||
index[1] = Math.floor(index[1])
|
||||
index[2] = Math.floor(index[2])
|
||||
this.mousePosition.index = index
|
||||
const modality = metadata.Modality
|
||||
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
|
||||
ijk = vec3.round(ijk, ijk);
|
||||
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
|
||||
this.isHandleOutsideImage = false
|
||||
let value = voxelManager.getAtIJKPoint(ijk)
|
||||
ijk[2] = viewport.getCurrentImageIdIndex()
|
||||
let modalityUnit
|
||||
if (modality === 'US') {
|
||||
const calibratedResults = cornerstoneTools.utilities.getCalibratedProbeUnitsAndValue(image, [ijk])
|
||||
const hasEnhancedRegionValues = calibratedResults.values.every(
|
||||
(value) => value !== null
|
||||
)
|
||||
value = (hasEnhancedRegionValues ? calibratedResults.values : value)
|
||||
modalityUnit = hasEnhancedRegionValues
|
||||
? calibratedResults.units
|
||||
: 'raw';
|
||||
} else {
|
||||
const scalingModule = referencedImageId && metaData.get('scalingModule', referencedImageId)
|
||||
const isSuvScaled = typeof scalingModule?.suvbw === 'number'
|
||||
const pixelUnitsOptions = {
|
||||
isPreScaled: cornerstoneTools.utilities.viewport.isViewportPreScaled(viewport, referencedImageId),
|
||||
isSuvScaled: isSuvScaled,
|
||||
}
|
||||
modalityUnit = cornerstoneTools.utilities.getPixelValueUnits(
|
||||
modality,
|
||||
referencedImageId,
|
||||
pixelUnitsOptions
|
||||
);
|
||||
}
|
||||
this.mousePosition.value = value
|
||||
this.mousePosition.modalityUnit = modalityUnit
|
||||
}
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
toggleTask(evt, visitTaskNum, i) {
|
||||
this.$emit('activeViewport', this.viewportIndex)
|
||||
|
|
@ -614,8 +779,10 @@ export default {
|
|||
clickSlider(e) {
|
||||
const height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
|
||||
this.sliderInfo.height = height
|
||||
let sliceIdx = Math.trunc(this.series.Stack.length * height / 100)
|
||||
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
|
||||
const totalSlices = this.imageInfo.total || this.series.Stack?.length || 1
|
||||
const maxIndex = totalSlices > 1 ? totalSlices - 1 : 0
|
||||
let sliceIdx = Math.round(maxIndex * height / 100)
|
||||
sliceIdx = sliceIdx > maxIndex ? maxIndex : sliceIdx < 0 ? 0 : sliceIdx
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportId
|
||||
|
|
@ -630,7 +797,7 @@ export default {
|
|||
},
|
||||
sliderMousedown(e) {
|
||||
const boxHeight = this.$refs['sliderBox'].clientHeight
|
||||
this.sliderInfo.oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
|
||||
this.sliderInfo.oldB = this.sliderInfo.height * boxHeight / 100
|
||||
this.sliderInfo.oldM = e.clientY
|
||||
this.sliderInfo.isMove = true
|
||||
e.stopImmediatePropagation()
|
||||
|
|
@ -644,8 +811,10 @@ export default {
|
|||
if (delta < 0) return
|
||||
if (delta > boxHeight) return
|
||||
const height = delta * 100 / boxHeight
|
||||
let sliceIdx = Math.trunc(this.series.Stack.length * height / 100)
|
||||
sliceIdx = sliceIdx >= this.series.Stack.length ? this.series.Stack.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
|
||||
const totalSlices = this.imageInfo.total || this.series.Stack?.length || 1
|
||||
const maxIndex = totalSlices > 1 ? totalSlices - 1 : 0
|
||||
let sliceIdx = Math.round(maxIndex * height / 100)
|
||||
sliceIdx = sliceIdx > maxIndex ? maxIndex : sliceIdx < 0 ? 0 : sliceIdx
|
||||
this.sliderInfo.height = height
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
|
|
@ -751,11 +920,15 @@ export default {
|
|||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.series = null
|
||||
this.topFusionVolumeActor = null
|
||||
},
|
||||
computed: {
|
||||
NSTip() {
|
||||
return `NS: ${this.$store.state.trials.uploadTip}`
|
||||
return `NS: ${this.$store.state.trials.downloadTip}`
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -767,6 +940,43 @@ export default {
|
|||
position: relative;
|
||||
cursor: default !important;
|
||||
|
||||
.opacity-slider-wrapper {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
width: 40px;
|
||||
|
||||
.slider-title {
|
||||
font-size: 12px;
|
||||
color: #ddd;
|
||||
margin-bottom: 10px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.opacity-slider {
|
||||
-webkit-appearance: slider-vertical;
|
||||
appearance: slider-vertical;
|
||||
writing-mode: vertical-lr;
|
||||
direction: rtl;
|
||||
width: 10px;
|
||||
height: 150px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.left-top-text {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
|
|
@ -826,6 +1036,20 @@ export default {
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.fusion-order-toggle {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
z-index: 2;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.left-bottom-text {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
|
|
@ -943,4 +1167,4 @@ export default {
|
|||
cursor: move
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1986,12 +1986,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -2002,7 +2002,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -2010,7 +2010,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -2021,7 +2021,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,259 @@
|
|||
<template>
|
||||
<el-form ref="segmentForm" :model="form" label-width="120px" label-position="left" :rules="rules">
|
||||
<!-- 检查名称 -->
|
||||
<el-form-item :label="$t('segment:form:label:studyName')" prop="taskBlindName">
|
||||
<el-select v-model="form.studyId" clearable @change="(e) => handleChange(e, 'study')">
|
||||
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode"
|
||||
:value="item.StudyId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 序列名称 -->
|
||||
<el-form-item :label="$t('segment:form:label:seriesName')" prop="taskBlindName">
|
||||
<el-select v-model="form.seriesId" clearable @change="(e) => handleChange(e, 'series')">
|
||||
<el-option v-for="item in seriesList" :key="item.Id"
|
||||
:label="`#${item.SeriesNumber}/${item.Modality}/${item.Description}`" :value="item.Id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 分割组名称 -->
|
||||
<el-form-item :label="$t('segment:form:label:segmentGroupName')" prop="segmentGroupId">
|
||||
<el-select v-model="form.segmentGroupId" clearable @change="(e) => handleChange(e, 'segmentGroup')">
|
||||
<el-option v-for="item in segmentGroupList" :key="item.Id" :label="item.SegmentationName"
|
||||
:value="item.Id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 分割名称 -->
|
||||
<el-form-item :label="$t('segment:form:label:segmentName')" prop="taskBlindName">
|
||||
<el-select v-model="form.segmentId" clearable>
|
||||
<el-option v-for="item in segmentList" :key="item.Id" :label="item.SegmentName" :value="item.Id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="text-align:right;">
|
||||
<!-- 取消 -->
|
||||
<el-button size="mini" @click="handleCancel">{{ $t('common:button:cancel') }}</el-button>
|
||||
<!-- 确认 -->
|
||||
<el-button type="primary" size="mini" @click="handleSave" :disabled="!form.segmentId">
|
||||
{{ $t('common:button:confirm') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
import { getSegmentationList, getSegmentList, getSegmentBindingList } from '@/api/reading'
|
||||
export default {
|
||||
name: 'FusionForm',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
visitInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
studyId: '',
|
||||
seriesId: '',
|
||||
segmentGroupId: '',
|
||||
segmentId: ""
|
||||
},
|
||||
studyList: [],
|
||||
seriesList: [],
|
||||
segmentGroupList: [],
|
||||
segmentList: [],
|
||||
segmentionList: [],
|
||||
series: {},
|
||||
rules: {
|
||||
segmentGroupId: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value) {
|
||||
// callback(new Error('请再次输入密码'));
|
||||
let segmentGroup = this.segmentGroupList.find(item => item.Id === value)
|
||||
if (!segmentGroup.IsSaved) {
|
||||
callback(new Error(this.$t('segment:confirm:message:segmentGroupIsNotSave')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}, trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
// mounted() {
|
||||
// this.init()
|
||||
// },
|
||||
methods: {
|
||||
setSeries(series) {
|
||||
this.series = series
|
||||
},
|
||||
async init() {
|
||||
let studyList = Object.assign(this.visitInfo.StudyList, {})
|
||||
let s = await this.getSegmentationList()
|
||||
this.segmentionList = s
|
||||
let StudyIds = s.map(item => item.StudyId)
|
||||
let SeriesIds = s.map(item => item.SeriesId)
|
||||
studyList = studyList.filter(item => StudyIds.includes(item.StudyId))
|
||||
studyList.forEach(study => {
|
||||
study.SeriesArr = study.SeriesList.filter(item => SeriesIds.includes(item.Id))
|
||||
})
|
||||
this.studyList = studyList
|
||||
if (this.visitInfo.operateStateEnum === 21) {
|
||||
this.form.studyId = this.series.StudyId
|
||||
this.handleChange(null, 'study')
|
||||
this.form.seriesId = this.series.Id
|
||||
this.handleChange(null, 'series')
|
||||
}
|
||||
if (this.visitInfo.operateStateEnum === 22) {
|
||||
let o = {}
|
||||
if (this.isTableQuestion) {
|
||||
o.TableQuestionId = this.visitInfo.operateQuestionId
|
||||
o.RowId = this.visitInfo.operateRowId
|
||||
} else {
|
||||
o.QuestionId = this.visitInfo.operateQuestionId
|
||||
}
|
||||
let list = await this.getSegmentBindingList(o)
|
||||
let segmentGroup = this.segmentionList.find(item => item.Id === list[0].SegmentationId)
|
||||
this.form.studyId = segmentGroup.StudyId
|
||||
this.handleChange(null, 'study')
|
||||
this.form.seriesId = segmentGroup.SeriesId
|
||||
this.handleChange(null, 'series')
|
||||
this.form.segmentGroupId = list[0].SegmentationId
|
||||
this.handleChange(null, 'segmentGroup')
|
||||
this.form.segmentId = list[0].SegmentId
|
||||
}
|
||||
},
|
||||
async handleChange(e, key) {
|
||||
if (key === 'study') {
|
||||
this.seriesList = this.studyList.find(item => item.StudyId === this.form.studyId).SeriesArr
|
||||
}
|
||||
if (key === 'series') {
|
||||
this.segmentGroupList = this.segmentionList.filter(item => item.SeriesId === this.form.seriesId)
|
||||
}
|
||||
if (key === 'segmentGroup') {
|
||||
let list = await this.getSegmentList(this.form.segmentGroupId)
|
||||
this.segmentList = list.filter(item => item.SegmentJson)
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.$emit("update:visible", false)
|
||||
},
|
||||
async handleSave() {
|
||||
let validate = await this.$refs.segmentForm.validate()
|
||||
if (!validate) return false
|
||||
let segment = this.segmentList.find(item => item.Id === this.form.segmentId)
|
||||
if (segment.SegmentJson) {
|
||||
let obj = JSON.parse(segment.SegmentJson)
|
||||
segment.stats = obj.stats
|
||||
segment.bidirectional = obj.bidirectional
|
||||
}
|
||||
this.$emit('handleSegmentSave', segment)
|
||||
this.handleCancel()
|
||||
},
|
||||
// 获取分割组
|
||||
async getSegmentationList() {
|
||||
try {
|
||||
let data = {
|
||||
VisitTaskId: this.visitInfo.VisitTaskId,
|
||||
PageSize: 9999,
|
||||
PageIndex: 1,
|
||||
}
|
||||
this.loading = true;
|
||||
let res = await getSegmentationList(data);
|
||||
this.loading = false;
|
||||
if (res.IsSuccess) {
|
||||
return res.Result.CurrentPageData;
|
||||
|
||||
}
|
||||
} catch (err) {
|
||||
this.loading = false
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
// 获取分割
|
||||
async getSegmentList(id) {
|
||||
try {
|
||||
let data = {
|
||||
SegmentationId: id,
|
||||
PageSize: 9999,
|
||||
PageIndex: 1,
|
||||
}
|
||||
this.loading = true;
|
||||
let res = await getSegmentList(data)
|
||||
this.loading = false;
|
||||
if (res.IsSuccess) {
|
||||
return res.Result.CurrentPageData
|
||||
}
|
||||
} catch (err) {
|
||||
this.loading = false
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
// 获取当前任务分割标记与问题绑定关系
|
||||
async getSegmentBindingList(param = {}) {
|
||||
try {
|
||||
let data = {
|
||||
VisitTaskId: this.visitInfo.VisitTaskId,
|
||||
PageSize: 9999,
|
||||
PageIndex: 1,
|
||||
}
|
||||
data = Object.assign(data, param)
|
||||
let res = await getSegmentBindingList(data)
|
||||
if (res.IsSuccess) {
|
||||
return res.Result.CurrentPageData
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.series-table {
|
||||
::v-deep.el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
}
|
||||
|
||||
.el-table--border::after,
|
||||
.el-table--group::after,
|
||||
.el-table::before {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
}
|
||||
|
||||
tr:hover>td {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,16 +1,10 @@
|
|||
<template>
|
||||
<div v-loading="loading" class="study-wrapper">
|
||||
<div class="study-info">
|
||||
<div
|
||||
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
|
||||
:title="taskInfo.SubjectCode"
|
||||
>
|
||||
<div v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo" :title="taskInfo.SubjectCode">
|
||||
{{ taskInfo.SubjectCode }}
|
||||
</div>
|
||||
<div
|
||||
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
|
||||
:title="visitTaskInfo.TaskBlindName"
|
||||
>
|
||||
<div v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo" :title="visitTaskInfo.TaskBlindName">
|
||||
{{ visitTaskInfo.TaskBlindName }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -18,15 +12,14 @@
|
|||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
|
||||
<template slot="title">
|
||||
<div
|
||||
v-if="!study.IsCriticalSequence"
|
||||
class="dicom-desc"
|
||||
>
|
||||
<div v-if="!study.IsCriticalSequence" class="dicom-desc">
|
||||
<template v-if="taskInfo && taskInfo.IsShowStudyName">
|
||||
<div style="text-overflow: ellipsis;overflow: hidden;">
|
||||
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
|
||||
<span v-if="study.StudyName" :title="study.StudyName" style="margin-left: 5px;">{{ study.StudyName }}</span>
|
||||
<span v-else :title="study.Modalities" style="margin-left: 5px;">{{ `${study.Modalities} (${study.SeriesCount})` }}</span>
|
||||
<span v-if="study.StudyName" :title="study.StudyName" style="margin-left: 5px;">{{ study.StudyName
|
||||
}}</span>
|
||||
<span v-else :title="study.Modalities" style="margin-left: 5px;">{{ `${study.Modalities}
|
||||
(${study.SeriesCount})` }}</span>
|
||||
</div>
|
||||
<div v-if="study.StudyName" style="text-overflow: ellipsis;overflow: hidden;">
|
||||
<span :title="study.Modalities">{{ `${study.Modalities} (${study.SeriesCount})` }}</span>
|
||||
|
|
@ -46,53 +39,38 @@
|
|||
</div>
|
||||
</template>
|
||||
<div class="dicom-list-container">
|
||||
<div
|
||||
v-for="(series, i) in study.SeriesList"
|
||||
:key="series.Id"
|
||||
style="position:relative;margin-top:1px;"
|
||||
@click="activeSeries(series, i, index)"
|
||||
>
|
||||
<div
|
||||
:class="{'series-active': index === activeStudyIndex && i === activeSeriesIndex}"
|
||||
class="series-wrapper"
|
||||
>
|
||||
<div v-for="(series, i) in study.SeriesList" :key="series.Id" style="position:relative;margin-top:1px;"
|
||||
@click="activeSeries(series, i, index)">
|
||||
<div :class="{ 'series-active': index === activeStudyIndex && i === activeSeriesIndex }"
|
||||
class="series-wrapper">
|
||||
<div class="series-image">
|
||||
<el-image
|
||||
style="width: 100%;height: 100%;"
|
||||
<el-image style="width: 100%;height: 100%;"
|
||||
:src="`${OSSclientConfig.basePath}${series.ImageResizePath || series.NoneDicomFileFirstFile}`"
|
||||
fit="fill"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
fit="fill" crossorigin="anonymous" />
|
||||
</div>
|
||||
<div class="series-text">
|
||||
<div v-if="series.IsExistMutiFrames && series.InstanceCount > 1"
|
||||
style="position: absolute;right: 0;top: 0;">
|
||||
<el-popover
|
||||
placement="right"
|
||||
trigger="hover"
|
||||
popper-class="instance_frame_wrapper"
|
||||
>
|
||||
<div v-if="series.IsExistMutiFrames && series.InstanceCount > 1"
|
||||
style="position: absolute;right: 0;top: 0;">
|
||||
<el-popover placement="right" trigger="hover" popper-class="instance_frame_wrapper">
|
||||
<div class="frame_list">
|
||||
<div
|
||||
v-for="(instance, idx) in series.InstanceInfoList"
|
||||
:key="instance.Id"
|
||||
class="frame_content"
|
||||
:style="{'margin-bottom':idx<series.InstanceInfoList.length-1? '5px':'0px'}"
|
||||
@click.stop="showMultiFrames(index,series, i, instance)"
|
||||
>
|
||||
<div v-for="(instance, idx) in series.InstanceInfoList" :key="instance.Id" class="frame_content"
|
||||
:style="{ 'margin-bottom': idx < series.InstanceInfoList.length - 1 ? '5px' : '0px' }"
|
||||
@click.stop="showMultiFrames(index, series, i, instance)">
|
||||
<div>
|
||||
<div>{{ instance.InstanceNumber }}</div>
|
||||
<div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ? instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div>
|
||||
<div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ?
|
||||
instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;color: #ffeb3b;" />
|
||||
<i slot="reference" class="el-icon-connection"
|
||||
style="font-size: 15px;cursor: pointer;color: #ffeb3b;" />
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="!study.IsCriticalSequence" class="text-desc" :title="series.SeriesNumber">
|
||||
#{{ series.SeriesNumber }}
|
||||
|
||||
|
||||
</div>
|
||||
<div v-if="series.Description" class="text-desc" :title="series.Description">
|
||||
{{ series.Description }}
|
||||
|
|
@ -104,17 +82,19 @@
|
|||
<span v-show="series.LoadedImageCount < series.InstanceCount">
|
||||
{{ series.Modality }}: {{ series.LoadedImageCount }}/{{ series.InstanceCount }} image
|
||||
</span>
|
||||
<span v-show="series.LoadedImageCount >= series.InstanceCount">{{ series.Modality }}: {{ series.InstanceCount }} image</span>
|
||||
<span v-show="series.LoadedImageCount >= series.InstanceCount">{{ series.Modality }}: {{
|
||||
series.InstanceCount
|
||||
}} image</span>
|
||||
</div>
|
||||
<div style="line-height: 12px;">
|
||||
<i v-show="series.IsBeMark || markedSeriesIds.includes(series.Id)" class="el-icon-star-on" style="font-size: 12px;color: #ff5722;" />
|
||||
<i v-show="series.IsBeMark || markedSeriesIds.includes(series.Id)" class="el-icon-star-on"
|
||||
style="font-size: 12px;color: #ff5722;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="series.LoadedImageCount > 0 && series.LoadedImageCount < series.InstanceCount" style="width: 100%;">
|
||||
<el-progress
|
||||
:percentage="parseInt((series.LoadedImageProgress / series.InstanceCount).toFixed(2))"
|
||||
/>
|
||||
<div v-if="series.LoadedImageCount > 0 && series.LoadedImageCount < series.InstanceCount"
|
||||
style="width: 100%;">
|
||||
<el-progress :percentage="parseInt((series.LoadedImageProgress / series.InstanceCount).toFixed(2))" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -124,6 +104,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
|
||||
export default {
|
||||
name: 'StudyList',
|
||||
props: {
|
||||
|
|
@ -168,6 +149,7 @@ export default {
|
|||
this.activeStudyIndex = studyIndex
|
||||
this.activeSeriesIndex = seriesIndex
|
||||
this.$emit('activeSeries', series)
|
||||
DicomEvent.$emit('activeSeries', series)
|
||||
},
|
||||
activeStudy(id) {
|
||||
if (this.activeNames.indexOf(id) > -1) return
|
||||
|
|
@ -179,6 +161,7 @@ export default {
|
|||
const studyId = this.studyList[studyIndex].StudyId
|
||||
if (!studyId) return
|
||||
this.activeStudy(studyId)
|
||||
DicomEvent.$emit('activeSeries', this.studyList[studyIndex].SeriesList[this.activeSeriesIndex])
|
||||
},
|
||||
showMultiFrames(studyIndex, series, seriesIndex, instance) {
|
||||
let obj = Object.assign({}, series)
|
||||
|
|
@ -203,7 +186,7 @@ export default {
|
|||
obj.ImageIds = imageIds
|
||||
obj.SliceIndex = 0
|
||||
this.$emit('showMultiFrame', obj)
|
||||
|
||||
|
||||
},
|
||||
getPreviousOrNextSeries(type, series) {
|
||||
const seriseList = this.studyList.map(s => s.SeriesList).flat()
|
||||
|
|
@ -224,13 +207,14 @@ export default {
|
|||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.study-wrapper{
|
||||
width:100%;
|
||||
.study-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.study-info {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
|
|
@ -241,7 +225,8 @@ export default {
|
|||
background-color: #4c4c4c;
|
||||
height: 50px;
|
||||
}
|
||||
.dicom-desc{
|
||||
|
||||
.dicom-desc {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
|
@ -258,19 +243,22 @@ export default {
|
|||
touch-action: auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.series-active {
|
||||
background-color: #607d8b!important;
|
||||
border: 1px solid #607d8b!important;
|
||||
background-color: #607d8b !important;
|
||||
border: 1px solid #607d8b !important;
|
||||
}
|
||||
::v-deep.el-progress__text{
|
||||
::v-deep .el-progress__text{
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
.dicom-list-container{
|
||||
|
||||
.dicom-list-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.series-wrapper {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
|
|
@ -279,21 +267,26 @@ export default {
|
|||
align-items: center;
|
||||
cursor: pointer;
|
||||
background-color: #3a3a3a;
|
||||
.el-progress__text{
|
||||
|
||||
.el-progress__text {
|
||||
display: none;
|
||||
}
|
||||
.el-progress-bar{
|
||||
padding-right:0px;
|
||||
|
||||
.el-progress-bar {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.series-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.series-text {
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
color: #ddd;
|
||||
position: relative;
|
||||
|
||||
.text-desc {
|
||||
width: 100px;
|
||||
white-space: nowrap;
|
||||
|
|
@ -306,27 +299,31 @@ export default {
|
|||
|
||||
}
|
||||
}
|
||||
::v-deep.el-collapse{
|
||||
::v-deep .el-collapse{
|
||||
border: none;
|
||||
.el-collapse-item{
|
||||
background-color: #000!important;
|
||||
|
||||
.el-collapse-item {
|
||||
background-color: #000 !important;
|
||||
color: #ddd;
|
||||
|
||||
}
|
||||
.el-collapse-item__content{
|
||||
padding-bottom:0px;
|
||||
background-color: #000!important;
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0px;
|
||||
background-color: #000 !important;
|
||||
}
|
||||
.el-collapse-item__header{
|
||||
background-color: #000!important;
|
||||
|
||||
.el-collapse-item__header {
|
||||
background-color: #000 !important;
|
||||
color: #ddd;
|
||||
border-bottom-color:#5a5a5a;
|
||||
border-bottom-color: #5a5a5a;
|
||||
padding-left: 5px;
|
||||
// height: 50px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
::v-deep .el-progress-bar__inner{
|
||||
|
||||
::v-deep .el-progress-bar__inner {
|
||||
transition: width 0s ease;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@
|
|||
<div v-if="series" class="right-bottom-text">
|
||||
<div v-show="imageInfo.location">Location: {{
|
||||
`${Number(imageInfo.location).toFixed(digitPlaces)} mm`
|
||||
}}</div>
|
||||
}}</div>
|
||||
<div v-show="imageInfo.sliceThickness">Slice Thickness: {{
|
||||
`${Number(imageInfo.sliceThickness).toFixed(digitPlaces)} mm`
|
||||
}}</div>
|
||||
}}</div>
|
||||
<div v-show="imageInfo.wwwc">WW/WL: {{ imageInfo.wwwc }}</div>
|
||||
</div>
|
||||
<div class="orientation-top">
|
||||
|
|
@ -68,6 +68,8 @@
|
|||
<script>
|
||||
import {
|
||||
metaData,
|
||||
volumeLoader,
|
||||
setVolumesForViewports,
|
||||
getRenderingEngine,
|
||||
utilities as csUtils,
|
||||
cache
|
||||
|
|
@ -75,10 +77,12 @@ import {
|
|||
import * as cornerstoneTools from '@cornerstonejs/tools'
|
||||
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
|
||||
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
||||
import { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
|
||||
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
|
||||
import { vec3, mat4 } from 'gl-matrix'
|
||||
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
|
||||
export default {
|
||||
name: 'ImageViewport',
|
||||
name: 'MPRViewport',
|
||||
props: {
|
||||
renderingEngineId: {
|
||||
type: String,
|
||||
|
|
@ -92,12 +96,6 @@ export default {
|
|||
type: Number,
|
||||
required: true
|
||||
},
|
||||
MPRInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -125,7 +123,9 @@ export default {
|
|||
sliceThickness: null,
|
||||
wwwc: null,
|
||||
total: 0,
|
||||
sliceThickness: 0
|
||||
sliceThickness: 0,
|
||||
imageOrientationPatient: [],
|
||||
imagePositionPatient: []
|
||||
},
|
||||
digitPlaces: 2,
|
||||
orientationMarkers: [],
|
||||
|
|
@ -181,6 +181,7 @@ export default {
|
|||
// resizeObserver.observe(this.element)
|
||||
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
|
||||
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
|
||||
this.element.addEventListener('CORNERSTONE_IMAGE_RENDERED', this.imageRendered)
|
||||
this.element.addEventListener('wheel', (e) => {
|
||||
console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL')
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
|
|
@ -189,10 +190,10 @@ export default {
|
|||
const totalImages = this.imageInfo.total;
|
||||
|
||||
// 手动实现循环逻辑
|
||||
if (currentImageIdIndex >= totalImages - 1) {
|
||||
if (currentImageIdIndex >= totalImages - 1 && e.wheelDeltaY < 0) {
|
||||
// 滚动到最后一张时跳回第一张
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: 0 });
|
||||
} else if (currentImageIdIndex <= 0) {
|
||||
} else if (currentImageIdIndex <= 0 && e.wheelDeltaY > 0) {
|
||||
// 滚动到第一张时跳回最后一张
|
||||
csUtils.jumpToSlice(viewport.element, { imageIndex: totalImages - 1 });
|
||||
}
|
||||
|
|
@ -272,19 +273,31 @@ export default {
|
|||
const zoom = viewport.getZoom()
|
||||
this.imageInfo.zoom = zoom.toFixed(4)
|
||||
let imageIds = viewport.getImageIds(this.volumeId)
|
||||
let imageId = imageIds[0]
|
||||
let volume = cache.getVolume(this.volumeId)
|
||||
let { spacing } = volume
|
||||
// if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId()
|
||||
let imageId = imageIds[detail.imageIndex]
|
||||
if (imageId) {
|
||||
this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices })
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId)
|
||||
let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient)
|
||||
this.imageInfo.location = type === this.series.orientation ? imagePlaneModule.sliceLocation : ''
|
||||
this.imageInfo.sliceThickness = type === this.series.orientation ? spacing[2] : spacing[0]
|
||||
this.imageInfo.imageOrientationPatient = imagePlaneModule.imageOrientationPatient
|
||||
this.imageInfo.imagePositionPatient = imagePlaneModule.imagePositionPatient
|
||||
this.imageInfo.size = `${imagePlaneModule.columns}*${imagePlaneModule.rows}`
|
||||
this.imageInfo.location = imagePlaneModule.sliceLocation
|
||||
this.imageInfo.total = detail.numberOfSlices
|
||||
let type = this.determineImagePlane(imagePlaneModule.imageOrientationPatient)
|
||||
let volume = cache.getVolume(this.volumeId)
|
||||
let spacing = volume ? volume.spacing : []
|
||||
this.imageInfo.sliceThickness = type === 'AXIAL' ? spacing[2] : spacing[0]
|
||||
this.getOrientationMarker()
|
||||
let properties = viewport.getProperties(this.volumeId)
|
||||
if (this.series && this.series.Id) {
|
||||
let annotations = cornerstoneTools.annotation.state.getAllAnnotations().filter(item => item.metadata.toolName !== 'ScaleOverlay' && item.metadata.volumeId !== this.volumeId && !item.metadata.segmentationId && item.seriesId !== this.series.Id)
|
||||
annotations.forEach(item => {
|
||||
cornerstoneTools.annotation.state.removeAnnotation(item.annotationUID)
|
||||
})
|
||||
}
|
||||
this.$emit('renderAnnotations', this.series)
|
||||
let properties = viewport.getProperties()
|
||||
if (this.isFusion) {
|
||||
properties = viewport.getProperties(this.ptVolumeId)
|
||||
}
|
||||
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const windowWidth = upper - lower
|
||||
|
|
@ -293,15 +306,15 @@ export default {
|
|||
this.defaultWindowLevel.windowCenter = windowCenter
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
}
|
||||
const toolGroupId = this.viewportId
|
||||
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
|
||||
toolGroup.setToolEnabled('ScaleOverlay')
|
||||
}
|
||||
// const toolGroupId = this.viewportId
|
||||
const toolGroupId = 'share-viewport-volume'
|
||||
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
|
||||
toolGroup.setToolEnabled('ScaleOverlay')
|
||||
|
||||
},
|
||||
setFullScreen(index) {
|
||||
setTimeout(() => {
|
||||
this.series.SliceIndex = index
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportId
|
||||
|
|
@ -324,6 +337,22 @@ export default {
|
|||
this.$emit('upperRangeChange', Math.round(windowWidth))
|
||||
}
|
||||
},
|
||||
imageRendered(e) {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const properties = viewport.getProperties()
|
||||
|
||||
if (properties && properties.voiRange) {
|
||||
var { lower, upper } = properties.voiRange
|
||||
const { windowWidth, windowCenter } = csUtils.windowLevel.toWindowLevel(
|
||||
lower,
|
||||
upper
|
||||
)
|
||||
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
|
||||
}
|
||||
const zoom = viewport.getZoom()
|
||||
this.imageInfo.zoom = zoom.toFixed(4)
|
||||
},
|
||||
getOrientationMarker() {
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
|
|
@ -462,11 +491,28 @@ export default {
|
|||
},
|
||||
async createImageIdsAndCacheMetaData(obj) {
|
||||
this.loading = true
|
||||
await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
try {
|
||||
return await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getVolume(serie, isFusion = false) {
|
||||
return new Promise(async res => {
|
||||
let volumeId = `${isFusion ? 'fusion_' : ''}` + serie.SeriesInstanceUid;
|
||||
let volume = null;
|
||||
if (cache.getVolume(volumeId)) {
|
||||
volume = cache.getVolume(volumeId)
|
||||
} else {
|
||||
let imageIds = await this.createImageIdsAndCacheMetaData(serie)
|
||||
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: imageIds })
|
||||
volume.load()
|
||||
}
|
||||
res({ volumeId, volume })
|
||||
})
|
||||
this.loading = false
|
||||
},
|
||||
async setSeriesInfo(obj, isLocate = false) {
|
||||
try {
|
||||
|
|
@ -474,26 +520,34 @@ export default {
|
|||
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate && !data.isLocation) {
|
||||
data.SliceIndex = this.series.SliceIndex
|
||||
}
|
||||
// console.log(data)
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
|
||||
this.series = {}
|
||||
this.volumeId = data.SeriesInstanceUid
|
||||
let res = await this.getVolume(obj)
|
||||
this.volumeId = res.volumeId
|
||||
this.series = { ...data }
|
||||
viewport
|
||||
.setVolumes([{
|
||||
volumeId: this.volumeId, callback: (r) => {
|
||||
if (this.series.Modality === 'PT') {
|
||||
if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
|
||||
setPetColorMapTransferFunctionForVolumeActor(r, true)
|
||||
} else {
|
||||
const voi = metaData.get('voiLutModule', res.volume._imageIds[Math.ceil((res.volume._imageIds.length - 1) / 2)])
|
||||
setCtMappingRange(voi.windowWidth[0], voi.windowCenter[0])
|
||||
setCtTransferFunctionForVolumeActor(r)
|
||||
}
|
||||
console.log("渲染成功")
|
||||
}
|
||||
}]).then(res => {
|
||||
}]).then(r => {
|
||||
if (data.segment) {
|
||||
return DicomEvent.$emit("isloaded", { segment: data.segment, isChange: data.isChange })
|
||||
}
|
||||
if (data.isLocation) {
|
||||
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
|
||||
}
|
||||
DicomEvent.$emit("isloaded", { isChange: data.isChange })
|
||||
})
|
||||
viewport.render()
|
||||
} catch (e) {
|
||||
|
|
@ -506,13 +560,50 @@ export default {
|
|||
const worldPoint = currentPoints.world
|
||||
const renderingEngine = getRenderingEngine(this.renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(this.viewportId)
|
||||
const imageData = viewport.getImageData()
|
||||
if (!imageData) return
|
||||
const index = imageData.imageData.worldToIndex(worldPoint)
|
||||
let referencedImageId = viewport.getCurrentImageId()
|
||||
const data = viewport.getImageData()
|
||||
if (!data) return
|
||||
const { dimensions, imageData, metadata, voxelManager } = data
|
||||
const index = imageData.worldToIndex(worldPoint)
|
||||
index[0] = Math.floor(index[0])
|
||||
index[1] = Math.floor(index[1])
|
||||
index[2] = Math.floor(index[2])
|
||||
this.mousePosition.index = index
|
||||
const modality = metadata.Modality
|
||||
let ijk = csUtils.transformWorldToIndex(imageData, worldPoint)
|
||||
ijk = vec3.round(ijk, ijk);
|
||||
if (csUtils.indexWithinDimensions(ijk, dimensions)) {
|
||||
this.isHandleOutsideImage = false
|
||||
let value = voxelManager.getAtIJKPoint(ijk)
|
||||
ijk[2] = viewport.getCurrentImageIdIndex()
|
||||
let modalityUnit
|
||||
if (modality === 'US') {
|
||||
const calibratedResults = cornerstoneTools.utilities.getCalibratedProbeUnitsAndValue(image, [ijk])
|
||||
const hasEnhancedRegionValues = calibratedResults.values.every(
|
||||
(value) => value !== null
|
||||
)
|
||||
value = (hasEnhancedRegionValues ? calibratedResults.values : value)
|
||||
modalityUnit = hasEnhancedRegionValues
|
||||
? calibratedResults.units
|
||||
: 'raw';
|
||||
} else {
|
||||
const scalingModule = referencedImageId && metaData.get('scalingModule', referencedImageId)
|
||||
const isSuvScaled = typeof scalingModule?.suvbw === 'number'
|
||||
if (scalingModule && scalingModule.suvbw) {
|
||||
const pixelUnitsOptions = {
|
||||
isPreScaled: cornerstoneTools.utilities.viewport.isViewportPreScaled(viewport, referencedImageId),
|
||||
isSuvScaled: isSuvScaled,
|
||||
}
|
||||
modalityUnit = cornerstoneTools.utilities.getPixelValueUnits(
|
||||
modality,
|
||||
referencedImageId,
|
||||
pixelUnitsOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
this.mousePosition.value = value
|
||||
this.mousePosition.modalityUnit = modalityUnit
|
||||
}
|
||||
},
|
||||
toggleTask(evt, visitTaskNum, i) {
|
||||
this.$emit('activeViewport', this.viewportIndex)
|
||||
|
|
@ -629,7 +720,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
NSTip() {
|
||||
return `NS: ${this.$store.state.trials.uploadTip}`
|
||||
return `NS: ${this.$store.state.trials.downloadTip}`
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left:-1px;border: 1px solid #424242;">
|
||||
<el-input v-model="range" size="mini" style="width:120px" maxlength="3"
|
||||
<div v-if="modality !== 'NM'" style="margin-left:-1px;border: 1px solid #424242;">
|
||||
<el-input v-model="range" size="mini" style="width:120px" :maxlength="maxLength"
|
||||
oninput="if(value){value=value.replace(/[^\d]/g,'')} if(value<=0){value=''}"
|
||||
@change="upperRangeChange">
|
||||
<template slot="append">g/ml</template>
|
||||
|
|
@ -54,12 +54,24 @@ import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/C
|
|||
const { registerColormap, getColormapNames, getColormap } = csUtils.colormap
|
||||
export default {
|
||||
name: "colorMap",
|
||||
props: {
|
||||
modality: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxLength: {
|
||||
type: [Number, String],
|
||||
default: 6
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
colorMaps: [],
|
||||
rgbPresetName: 'siemens',
|
||||
range: 40,
|
||||
upper: 6,
|
||||
isSlideMoving: false,
|
||||
req: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -72,9 +84,24 @@ export default {
|
|||
this.renderColorMaps()
|
||||
this.upperRangeChange(this.range)
|
||||
this.initSlider()
|
||||
// this.syncSliderPosition()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
syncSliderPosition(retry = 0) {
|
||||
var sliderBox = document.getElementById('sliderBox')
|
||||
var container = document.getElementById('colorBarCanvas')
|
||||
if (!sliderBox || !container) return
|
||||
|
||||
var maxLeft = container.clientWidth - sliderBox.clientWidth
|
||||
if (maxLeft <= 0) {
|
||||
if (retry < 30) {
|
||||
requestAnimationFrame(() => this.syncSliderPosition(retry + 1))
|
||||
}
|
||||
return
|
||||
}
|
||||
this.updateSliderPosition()
|
||||
},
|
||||
renderColorMaps() {
|
||||
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
|
||||
this.colorMaps.forEach((e, index) => {
|
||||
|
|
@ -84,61 +111,127 @@ export default {
|
|||
voiChange(v) {
|
||||
this.$emit('voiChange', v)
|
||||
},
|
||||
changeVoi(v) {
|
||||
// 被外部组件调窗同步时调用
|
||||
if (v === this.upper) return
|
||||
|
||||
var range = Number(this.range) || 0
|
||||
var upper = Number(v) || 0
|
||||
if (range <= 0) {
|
||||
upper = 0
|
||||
} else if (upper > range) {
|
||||
upper = range
|
||||
} else if (upper < 0) {
|
||||
upper = 0
|
||||
}
|
||||
this.upper = upper
|
||||
this.syncSliderPosition()
|
||||
},
|
||||
initSlider() {
|
||||
var slider = document.getElementById('slider')
|
||||
var sliderBox = document.getElementById('sliderBox')
|
||||
var container = document.getElementById('colorBarCanvas')
|
||||
if (!slider || !sliderBox || !container) return
|
||||
|
||||
slider.addEventListener('mousedown', () => {
|
||||
this.isSlideMoving = true
|
||||
})
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (this.isSlideMoving) {
|
||||
var containerWidth = container.clientWidth
|
||||
var sliderWidth = sliderBox.clientWidth
|
||||
var maxLeft = containerWidth - sliderWidth
|
||||
var left = e.clientX - container.getBoundingClientRect().left
|
||||
var position = null
|
||||
position = left
|
||||
if (left < 0) {
|
||||
left = 6
|
||||
position = 0
|
||||
} else if (left > maxLeft) {
|
||||
left = maxLeft + 6
|
||||
position = maxLeft
|
||||
}
|
||||
if (this.req) return;
|
||||
this.req = requestAnimationFrame(() => {
|
||||
this.req = null;
|
||||
var containerWidth = container.clientWidth
|
||||
var sliderWidth = sliderBox.clientWidth
|
||||
var maxLeft = containerWidth - sliderWidth
|
||||
if (maxLeft <= 0) return
|
||||
|
||||
slider.style.left = left + 'px'
|
||||
var positionValue = document.getElementById('slider-position')
|
||||
var upper = this.range
|
||||
position = parseInt((position / maxLeft) * upper)
|
||||
positionValue.textContent = position
|
||||
this.upper = position
|
||||
this.voiChange(position)
|
||||
var left = e.clientX - container.getBoundingClientRect().left
|
||||
if (left < 0) {
|
||||
left = 0
|
||||
} else if (left > maxLeft) {
|
||||
left = maxLeft
|
||||
}
|
||||
var position = left
|
||||
|
||||
slider.style.left = (left + 6) + 'px'
|
||||
var positionValue = document.getElementById('slider-position')
|
||||
var upper = Number(this.range) || 0
|
||||
position = parseInt((position / maxLeft) * upper)
|
||||
if (position > upper) position = upper
|
||||
if (position < 0) position = 0
|
||||
|
||||
if (this.modality === 'NM') {
|
||||
positionValue.textContent = upper > 0 ? Math.round((position / upper) * 100) + '%' : '0%'
|
||||
} else {
|
||||
positionValue.textContent = position
|
||||
}
|
||||
this.upper = position
|
||||
this.voiChange(position)
|
||||
});
|
||||
}
|
||||
})
|
||||
document.addEventListener('mouseup', () => {
|
||||
this.isSlideMoving = false
|
||||
})
|
||||
},
|
||||
upperRangeChange(v) {
|
||||
if (v === 0 || v < this.upper) {
|
||||
return
|
||||
}
|
||||
updateSliderPosition() {
|
||||
var sliderBox = document.getElementById('sliderBox')
|
||||
var container = document.getElementById('colorBarCanvas')
|
||||
if (!sliderBox || !container) return
|
||||
|
||||
var containerWidth = container.clientWidth
|
||||
var sliderWidth = sliderBox.clientWidth
|
||||
var maxLeft = containerWidth - sliderWidth
|
||||
var left = (this.upper / this.range) * maxLeft
|
||||
if (maxLeft <= 0) return
|
||||
|
||||
var range = Number(this.range) || 0
|
||||
var upper = Number(this.upper) || 0
|
||||
if (range <= 0) {
|
||||
upper = 0
|
||||
} else if (upper > range) {
|
||||
upper = range
|
||||
} else if (upper < 0) {
|
||||
upper = 0
|
||||
}
|
||||
if (upper !== this.upper) {
|
||||
this.upper = upper
|
||||
}
|
||||
|
||||
var left = range > 0 ? (upper / range) * maxLeft : 0
|
||||
if (left < 0) {
|
||||
left = 6
|
||||
left = 0
|
||||
} else if (left >= maxLeft) {
|
||||
left = maxLeft + 6
|
||||
left = maxLeft
|
||||
}
|
||||
var slider = document.getElementById('slider')
|
||||
slider.style.left = left + 'px'
|
||||
if (slider) {
|
||||
slider.style.left = (left + 6) + 'px'
|
||||
}
|
||||
var positionValue = document.getElementById('slider-position')
|
||||
positionValue.textContent = this.upper
|
||||
if (positionValue) {
|
||||
if (this.modality === 'NM') {
|
||||
positionValue.textContent = range > 0 ? Math.round((upper / range) * 100) + '%' : '0%'
|
||||
} else {
|
||||
positionValue.textContent = upper
|
||||
}
|
||||
}
|
||||
},
|
||||
upperRangeChange(v) {
|
||||
if (this.upper === v) return
|
||||
var nextRange = Number(v) || 0
|
||||
if (nextRange <= 0) {
|
||||
this.upper = 0
|
||||
this.updateSliderPosition()
|
||||
this.voiChange(0)
|
||||
return
|
||||
}
|
||||
this.range = nextRange
|
||||
if (this.upper > nextRange) {
|
||||
this.upper = nextRange
|
||||
this.voiChange(this.upper)
|
||||
}
|
||||
this.syncSliderPosition()
|
||||
},
|
||||
createColorBar(rgbPresetName, elId, width, height) {
|
||||
var colorMap = null
|
||||
|
|
@ -185,6 +278,7 @@ export default {
|
|||
init() {
|
||||
this.createColorBar(this.rgbPresetName, 'colorBarCanvas', 256, 15)
|
||||
this.$emit("setColorMap", this.rgbPresetName)
|
||||
this.syncSliderPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -322,4 +416,4 @@ export default {
|
|||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
:parentQsId="parentQsId" :isBaseline="isBaseline" :reading-task-state="readingTaskState"
|
||||
:question-form="QuestionsForm" :visit-task-id="visitTaskId" :criterion-id="criterionId"
|
||||
:type="addOrEdit.type" :calculationList="calculationList" :questionsMarkStatus="questionsMarkStatus"
|
||||
:questionsSegmentMarkStatus="questionsSegmentMarkStatus"
|
||||
@formItemTableNumberChange="formItemTableNumberChange" @resetFormItemData="resetTableFormItemData"
|
||||
@setFormItemData="setFormTableItemData" @operateImageMarker="operateImageMarker" @save="save"
|
||||
@handleReadingChart="handleReadingChart" />
|
||||
|
|
@ -183,7 +184,49 @@
|
|||
<el-input v-if="question.Type === 'increment'" v-model="questionForm[question.Id]" disabled />
|
||||
<!-- 数值 -->
|
||||
<template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)">
|
||||
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;">
|
||||
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;"
|
||||
v-if="question.ImageMarkTypeEnum === 1">
|
||||
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
|
||||
@input="numberInput(question.Id)"
|
||||
@blur="questionsSegmentMarkStatus[question.Id] ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
|
||||
v-model="questionForm[question.Id]"
|
||||
:title="questionsSegmentMarkStatus[question.Id] ? `${questionsSegmentMarkStatus[question.Id].SegmentationName}\n${questionsSegmentMarkStatus[question.Id].SegmentName}\n${questionForm[question.Id]}` : question.Remark"
|
||||
:disabled="(questionsSegmentMarkStatus[question.Id] && question.ImageMarkEnum === 2) || question.ImageMarkEnum === 1 || readingTaskState === 2"
|
||||
style="width: 150px;margin-right: 5px;">
|
||||
<template v-if="question.Unit !== 0" slot="append">
|
||||
{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}
|
||||
</template>
|
||||
</el-input>
|
||||
<svg-icon v-if="question.ShowChartTypeEnum > 0 && taskInfo.IsReadingTaskViewInOrder === 1"
|
||||
icon-class="readingChart" class="svg-icon svg-readingChart" @click.stop="(e) => handleReadingChart({
|
||||
e,
|
||||
data: {
|
||||
QuestionId: question.Id,
|
||||
QuestionName: question.QuestionName
|
||||
}
|
||||
})" />
|
||||
<!-- 绑定 -->
|
||||
<el-button v-if="readingTaskState < 2 && (!questionsSegmentMarkStatus[question.Id])" size="mini" type="text"
|
||||
@click="operateImageMarker({ operateStateEnum: 21, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:bind') }}
|
||||
</el-button>
|
||||
<!-- 更改 -->
|
||||
<el-button v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[question.Id])" size="mini" type="text"
|
||||
@click="operateImageMarker({ operateStateEnum: 22, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:edit') }}
|
||||
</el-button>
|
||||
<!-- 查看 -->
|
||||
<el-button v-if="questionsSegmentMarkStatus[question.Id]" size="mini" type="text"
|
||||
@click="operateImageMarker({ operateStateEnum: 23, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:view') }}
|
||||
</el-button>
|
||||
<!-- 移除 -->
|
||||
<el-button v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[question.Id])" size="mini" type="text"
|
||||
@click="operateImageMarker({ operateStateEnum: 24, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:remove') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;" v-else>
|
||||
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
|
||||
@input="numberInput(question.Id)"
|
||||
@blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
|
||||
|
|
@ -312,9 +355,10 @@
|
|||
:isNoneDicom="isNoneDicom" :reading-task-state="readingTaskState" :question-form="questionForm"
|
||||
:visit-task-id="visitTaskId" :criterion-id="criterionId" :calculationList="calculationList"
|
||||
:questionMarkInfoList="questionMarkInfoList" :questionsMarkStatus="questionsMarkStatus"
|
||||
@formItemNumberChange="formItemNumberChange" @setFormItemData="setFormItemData"
|
||||
@resetFormItemData="resetFormItemData" @getQuestions="getQuestions" @operateImageMarker="operateImageMarker"
|
||||
@unBindAnnotationToQuestion="unBindAnnotationToQuestion" @handleReadingChart="handleReadingChart" />
|
||||
:questionsSegmentMarkStatus="questionsSegmentMarkStatus" @formItemNumberChange="formItemNumberChange"
|
||||
@setFormItemData="setFormItemData" @resetFormItemData="resetFormItemData" @getQuestions="getQuestions"
|
||||
@operateImageMarker="operateImageMarker" @unBindAnnotationToQuestion="unBindAnnotationToQuestion"
|
||||
@handleReadingChart="handleReadingChart" @saveSegmentBindingAndAnswer="saveSegmentBindingAndAnswer" />
|
||||
</template>
|
||||
|
||||
<!-- <base-model :config="addOrEdit"
|
||||
|
|
@ -397,6 +441,12 @@ export default {
|
|||
return {}
|
||||
}
|
||||
},
|
||||
questionsSegmentMarkStatus: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
isNoneDicom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
@ -556,8 +606,65 @@ export default {
|
|||
this.operateImageMarker({ operateStateEnum, question: this.question, picturePath })
|
||||
}
|
||||
})
|
||||
DicomEvent.$on('setTableQuestionAnswer', async (DATA) => {
|
||||
let { id, answer, ParentQsId, data } = DATA
|
||||
// console.log(ParentQsId, this.question.Id)
|
||||
if (this.question.Id === ParentQsId) {
|
||||
this.QuestionsForm[id] = answer
|
||||
if (data.RowId) {
|
||||
let i = this.AnswersList.findIndex(i => i.RowId === this.QuestionsForm.RowId)
|
||||
this.AnswersList[i][id] = this.QuestionsForm[id]
|
||||
this.$emit('setFormItemData', { key: this.question.Id, val: this.AnswersList, question: this.question })
|
||||
this.formItemNumberChange(this.question.Id, true)
|
||||
this.$emit('saveSegmentBindingAndAnswer', [data])
|
||||
} else {
|
||||
try {
|
||||
// loading = this.$loading({ fullscreen: true })
|
||||
let answers = []
|
||||
let reg = new RegExp(/^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/)
|
||||
for (const k in this.QuestionsForm) {
|
||||
if (reg.test(k)) {
|
||||
if (answers.findIndex(i => i.tableQuestionId === k) === -1) {
|
||||
answers.push({ tableQuestionId: k, answer: this.QuestionsForm[k] })
|
||||
}
|
||||
}
|
||||
}
|
||||
this.QuestionsList.forEach(k => {
|
||||
if (reg.test(k.Id)) {
|
||||
if (answers.findIndex(i => i.tableQuestionId === k.Id) === -1) {
|
||||
answers.push({ tableQuestionId: k.Id, answer: '' })
|
||||
}
|
||||
}
|
||||
})
|
||||
let params = {
|
||||
questionId: this.question.Id,
|
||||
RowIndex: this.questionForm[this.question.Id].length + 1,
|
||||
RowId: this.QuestionsForm.RowId ? this.QuestionsForm.RowId : '',
|
||||
visitTaskId: this.visitTaskId,
|
||||
trialId: this.$route.query.trialId,
|
||||
answerList: answers
|
||||
}
|
||||
let res = await submitTableQuestion(params)
|
||||
if (res.IsSuccess) {
|
||||
this.QuestionsForm.RowId = res.Result.RowId
|
||||
obj.rowId = res.Result.RowId
|
||||
data.RowId = res.Result.RowId
|
||||
this.AnswersList.push(this.QuestionsForm)
|
||||
this.$emit('setFormItemData', { key: this.question.Id, val: this.AnswersList, question: this.question })
|
||||
this.formItemNumberChange(this.question.Id, true)
|
||||
this.$emit('saveSegmentBindingAndAnswer', [data])
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
saveSegmentBindingAndAnswer(list) {
|
||||
this.$emit('saveSegmentBindingAndAnswer', list)
|
||||
},
|
||||
handleReadingChart(row) {
|
||||
this.$emit('handleReadingChart', row)
|
||||
},
|
||||
|
|
@ -1349,13 +1456,13 @@ export default {
|
|||
getAnnotationProp(annotation, prop) {
|
||||
if (!annotation) return
|
||||
let referencedImageId = null
|
||||
if (annotation.from) {
|
||||
referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? 0 : i).join(',')}`
|
||||
if (annotation.from || annotation.metadata.volumeId) {
|
||||
referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? Number(0).toFixed(3) : Number(i).toFixed(3)).join(',')}`
|
||||
} else {
|
||||
referencedImageId = annotation?.metadata?.referencedImageId
|
||||
}
|
||||
if (!referencedImageId) return null
|
||||
const cacheKey = annotation.from ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}`
|
||||
const cacheKey = annotation.from || annotation.metadata.volumeId ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}`
|
||||
const points = ['x', 'y', 'z'];
|
||||
const cachedStats = annotation.markTool === "ArrowAnnotate" ? annotation.data?.handles?.points[0] : annotation.data?.cachedStats?.[cacheKey]
|
||||
const hasProp = cachedStats
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@
|
|||
:isNoneDicom="isNoneDicom" :question="question" :question-form="questionForm"
|
||||
:reading-task-state="readingTaskState" :criterion-id="criterionId" :calculation-list="calculationList"
|
||||
:question-mark-info-list="questionMarkInfoList" :questions-mark-status="questionsMarkStatus"
|
||||
:is-baseline="isBaseLineTask" @resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData"
|
||||
@getQuestions="getQuestions" @operateImageMarker="operateImageMarker"
|
||||
@unBindAnnotationToQuestion="unBindAnnotationToQuestion" @handleReadingChart="handleReadingChart" />
|
||||
:questionsSegmentMarkStatus="questionsSegmentMarkStatus" :is-baseline="isBaseLineTask"
|
||||
@resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData" @getQuestions="getQuestions"
|
||||
@operateImageMarker="operateImageMarker" @unBindAnnotationToQuestion="unBindAnnotationToQuestion"
|
||||
@handleReadingChart="handleReadingChart" @saveSegmentBindingAndAnswer="saveSegmentBindingAndAnswer" />
|
||||
</template>
|
||||
|
||||
<el-form-item v-if="readingTaskState < 2">
|
||||
|
|
@ -58,7 +59,7 @@
|
|||
<script>
|
||||
|
||||
import { getCustomTableQuestionAnswer, changeDicomReadingQuestionAnswer, submitVisitTaskQuestionsInDto, verifyVisitTaskQuestions, getQuestionCalculateRelation, saveTaskQuestion } from '@/api/trials'
|
||||
import { setSkipReadingCache, resetReadingTask, saveTableQuestionMark, saveAnswerAndBindingNoneDicomMark, changePlottingScaleChangeAnswer } from '@/api/reading'
|
||||
import { setSkipReadingCache, resetReadingTask, saveTableQuestionMark, saveAnswerAndBindingNoneDicomMark, changePlottingScaleChangeAnswer, getSegmentBindingList, saveSegmentBindingAndAnswer, getSegmentList, getSegmentationList } from '@/api/reading'
|
||||
import const_ from '@/const/sign-code'
|
||||
import QuestionFormItem from './QuestionFormItem'
|
||||
import SignForm from '@/views/trials/components/newSignForm'
|
||||
|
|
@ -116,6 +117,7 @@ export default {
|
|||
imageTool: '',
|
||||
imageToolAttribute: '',
|
||||
questionsMarkStatus: {},
|
||||
questionsSegmentMarkStatus: {},
|
||||
digitPlaces: 2,
|
||||
questionImageToolAttributeInfo: {},
|
||||
unSaveTargets: [],
|
||||
|
|
@ -133,6 +135,7 @@ export default {
|
|||
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
|
||||
this.getQuestionCalculateRelation()
|
||||
this.getQuestions(true)
|
||||
this.initSegmentBinding()
|
||||
DicomEvent.$on('opentableCol', (data) => {
|
||||
let { visible } = data
|
||||
this.isTableVisible = visible
|
||||
|
|
@ -433,8 +436,13 @@ export default {
|
|||
REMOVE: 4, // 移除标记
|
||||
SAVE_OUTER: 5, // 保存外层标记
|
||||
UPDATE: 6, // 更改数值内容
|
||||
SAVE_TABLE: 7 // 保存表格标记
|
||||
SAVE_TABLE: 7, // 保存表格标记
|
||||
BIND_SEGMENT: 21, // 绑定分割标记
|
||||
CHANGE_SEGMENT: 22, // 更改分割标记
|
||||
VIEW_SEGMENT: 23, // 查看分割标记
|
||||
REMOVE_SEGMENT: 24, // 移除分割标记
|
||||
}
|
||||
// console.log(obj, 'obj')
|
||||
const { question, operateStateEnum, rowId, answer } = obj
|
||||
const { Id, IsTableQuestion, ImageTool, ImageToolAttribute, ParentQsId, RowId, QuestionName, QuestionEnName } = question
|
||||
|
||||
|
|
@ -446,7 +454,7 @@ export default {
|
|||
operateQuestionId: Id,
|
||||
operateQuestionName: this.isEN ? QuestionEnName : QuestionName,
|
||||
operateRowId: RowId,
|
||||
operateParentQsId: ParentQsId
|
||||
operateParentQsId: ParentQsId,
|
||||
})
|
||||
const stateHandlers = {
|
||||
[STATE.BIND]: () => this.$emit('setReadingToolPassive'),
|
||||
|
|
@ -456,12 +464,182 @@ export default {
|
|||
[STATE.REMOVE]: this.handleRemoveAnnotation,
|
||||
[STATE.SAVE_OUTER]: this.isNoneDicom ? this.handleSaveNoneDicomOuterQuestions : this.handleSaveOuterQuestions,
|
||||
[STATE.UPDATE]: this.handleUpdateValue,
|
||||
[STATE.SAVE_TABLE]: this.isNoneDicom ? this.handleSaveNoneDicomTableQuestions : this.handleSaveTableQuestions
|
||||
[STATE.SAVE_TABLE]: this.isNoneDicom ? this.handleSaveNoneDicomTableQuestions : this.handleSaveTableQuestions,
|
||||
[STATE.BIND_SEGMENT]: this.handleBindSegment,
|
||||
[STATE.CHANGE_SEGMENT]: this.handleBindSegment,
|
||||
[STATE.VIEW_SEGMENT]: this.handleViewSegment,
|
||||
[STATE.REMOVE_SEGMENT]: this.handleRemoveSegment,
|
||||
}
|
||||
|
||||
const handler = stateHandlers[operateStateEnum]
|
||||
handler && await handler.call(this, obj)
|
||||
},
|
||||
async handleViewSegment(obj) {
|
||||
try {
|
||||
let o = {}
|
||||
if (this.isTableQuestion) {
|
||||
o.TableQuestionId = this.operateQuestionId
|
||||
o.RowId = this.operateRowId
|
||||
} else {
|
||||
o.QuestionId = this.operateQuestionId
|
||||
}
|
||||
let list = await this.getSegmentBindingList(o)
|
||||
if (list && list.length > 0) {
|
||||
let segment = await this.getSegmentList(list[0].SegmentId)
|
||||
if (segment[0].SegmentJson) {
|
||||
let obj = JSON.parse(segment[0].SegmentJson)
|
||||
segment[0].stats = obj.stats
|
||||
segment[0].bidirectional = obj.bidirectional
|
||||
segment[0].segmentationId = segment[0].SegmentationId
|
||||
segment[0].segmentIndex = segment[0].SegmentNumber
|
||||
}
|
||||
let segmentGroup = await this.getSegmentationList(list[0].SegmentationId)
|
||||
this.$emit('viewCustomAnnotationSeries', {
|
||||
visitTaskId: this.visitTaskId,
|
||||
segment: segment[0],
|
||||
segmentGroup: segmentGroup[0]
|
||||
})
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
handleBindSegment(obj) {
|
||||
this.$emit("openSegmentForm", { visitInfo: this.visitInfo, operateStateEnum: obj.operateStateEnum, isTableQuestion: this.isTableQuestion, operateQuestionId: this.operateQuestionId, operateRowId: this.operateRowId })
|
||||
},
|
||||
handleSegmentSave(obj) {
|
||||
let imageToolAttribute = this.imageToolAttribute
|
||||
let answer = ''
|
||||
if (imageToolAttribute === 'length' || imageToolAttribute === 'width') {
|
||||
let s = {
|
||||
length: "maxMajor",
|
||||
width: 'maxMinor'
|
||||
}
|
||||
if (!obj.bidirectional || !obj.bidirectional.maxMajor) return this.$confirm(this.$t("segment:error:notValue"))
|
||||
answer = obj.bidirectional[s[imageToolAttribute]] ? Number(obj.bidirectional[s[imageToolAttribute]]).toFixed(this.digitPlaces) : ''
|
||||
} else {
|
||||
if (!obj.stats) return this.$confirm(this.$t("segment:error:notValue"))
|
||||
answer = obj.stats[imageToolAttribute] ? Number((obj.stats[imageToolAttribute]).value).toFixed(this.digitPlaces) : ''
|
||||
}
|
||||
let o = {
|
||||
Answer: answer,
|
||||
SegmentId: obj.Id,
|
||||
SegmentationId: obj.SegmentationId,
|
||||
QuestionId: this.isTableQuestion ? this.operateParentQsId : this.operateQuestionId,
|
||||
RowId: this.isTableQuestion ? this.operateRowId : null,
|
||||
TableQuestionId: this.isTableQuestion ? this.operateQuestionId : null,
|
||||
VisitTaskId: this.visitTaskId,
|
||||
}
|
||||
if (this.isTableQuestion) {
|
||||
return DicomEvent.$emit('setTableQuestionAnswer', { id: this.operateQuestionId, answer, ParentQsId: this.operateParentQsId, data: o })
|
||||
} else {
|
||||
this.$set(this.questionForm, this.operateQuestionId, answer)
|
||||
}
|
||||
this.saveSegmentBindingAndAnswer([o])
|
||||
},
|
||||
// 删除问题与分割标记绑定
|
||||
async handleRemoveSegment(obj) {
|
||||
const { question } = obj
|
||||
let confirm = await this.$confirm(this.$t('segment:confirm:sureDelete'))
|
||||
if (!confirm) return false
|
||||
this.$set(this.questionForm, question.Id, '')
|
||||
let o = {
|
||||
Answer: '',
|
||||
SegmentId: null,
|
||||
SegmentationId: null,
|
||||
QuestionId: this.isTableQuestion ? this.operateParentQsId : this.operateQuestionId,
|
||||
RowId: this.isTableQuestion ? this.operateRowId : null,
|
||||
TableQuestionId: this.isTableQuestion ? this.operateQuestionId : null,
|
||||
VisitTaskId: this.visitTaskId,
|
||||
}
|
||||
this.saveSegmentBindingAndAnswer([o])
|
||||
},
|
||||
// 添加分割标记与问题绑定关系
|
||||
async saveSegmentBindingAndAnswer(list) {
|
||||
try {
|
||||
let data = {
|
||||
VisitTaskId: this.visitTaskId,
|
||||
BindingList: list
|
||||
}
|
||||
let res = await saveSegmentBindingAndAnswer(data)
|
||||
if (res.IsSuccess) {
|
||||
this.initSegmentBinding()
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
// 初始化分割标记与问题绑定关系
|
||||
async initSegmentBinding() {
|
||||
try {
|
||||
let list = await this.getSegmentBindingList()
|
||||
this.questionsSegmentMarkStatus = {}
|
||||
list.forEach(item => {
|
||||
if (item.TableQuestionId && item.RowId) {
|
||||
this.$set(this.questionsSegmentMarkStatus, `${item.RowId}_${item.TableQuestionId}`, { SegmentId: item.SegmentId, SegmentationName: item.SegmentationName, SegmentName: item.SegmentName, })
|
||||
} else {
|
||||
this.$set(this.questionsSegmentMarkStatus, item.QuestionId, { SegmentId: item.SegmentId, SegmentationName: item.SegmentationName, SegmentName: item.SegmentName, })
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
// 获取当前任务分割标记与问题绑定关系
|
||||
async getSegmentBindingList(param = {}) {
|
||||
try {
|
||||
let data = {
|
||||
VisitTaskId: this.visitTaskId,
|
||||
PageSize: 9999,
|
||||
PageIndex: 1,
|
||||
}
|
||||
data = Object.assign(data, param)
|
||||
let res = await getSegmentBindingList(data)
|
||||
if (res.IsSuccess) {
|
||||
return res.Result.CurrentPageData
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
// 获取分割
|
||||
async getSegmentList(id) {
|
||||
try {
|
||||
let data = {
|
||||
// SegmentationId: id,
|
||||
PageSize: 9999,
|
||||
PageIndex: 1,
|
||||
}
|
||||
if (id) data.Id = id
|
||||
let res = await getSegmentList(data)
|
||||
if (res.IsSuccess) {
|
||||
return res.Result.CurrentPageData
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
// 获取分割组
|
||||
async getSegmentationList(id) {
|
||||
try {
|
||||
let data = {
|
||||
VisitTaskId: this.visitInfo.VisitTaskId,
|
||||
PageSize: 9999,
|
||||
PageIndex: 1,
|
||||
}
|
||||
if (id) data.Id = id
|
||||
let res = await getSegmentationList(data);
|
||||
|
||||
if (res.IsSuccess) {
|
||||
return res.Result.CurrentPageData;
|
||||
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
},
|
||||
|
||||
async handleViewAnnotation(obj) {
|
||||
const index = this.findMarkIndex(obj.question)
|
||||
if (index === -1) return
|
||||
|
|
@ -1049,13 +1227,13 @@ export default {
|
|||
getAnnotationProp(annotation, prop) {
|
||||
if (!annotation || !prop) return
|
||||
let referencedImageId = null
|
||||
if (annotation.from) {
|
||||
referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? 0 : i).join(',')}`
|
||||
if (annotation.from || annotation.metadata.volumeId) {
|
||||
referencedImageId = `${annotation?.metadata?.volumeId}?sliceIndex=${annotation?.metadata?.sliceIndex}&viewPlaneNormal=${annotation?.metadata?.viewPlaneNormal.map(i => i == 0 ? Number(0).toFixed(3) : Number(i).toFixed(3)).join(',')}`
|
||||
} else {
|
||||
referencedImageId = annotation?.metadata?.referencedImageId
|
||||
}
|
||||
if (!referencedImageId) return null
|
||||
const cacheKey = annotation.from ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}`
|
||||
const cacheKey = annotation.from || annotation.metadata.volumeId ? `volumeId:${referencedImageId}` : `imageId:${referencedImageId}`
|
||||
const points = ['x', 'y', 'z']
|
||||
const cachedStats = annotation.markTool === "ArrowAnnotate" ? annotation.data?.handles?.points[0] : annotation.data?.cachedStats?.[cacheKey]
|
||||
const hasProp = cachedStats
|
||||
|
|
|
|||
|
|
@ -107,7 +107,53 @@
|
|||
<el-input v-if="question.Type === 'increment'" v-model="questionForm[question.Id]" disabled />
|
||||
<!-- 数值 -->
|
||||
<template v-if="question.Type === 'number' && (question.ImageMarkEnum === 1 || question.ImageMarkEnum === 2)">
|
||||
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;">
|
||||
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;"
|
||||
v-if="question.ImageMarkTypeEnum === 1">
|
||||
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
|
||||
@input="numberInput(question.Id)"
|
||||
@blur="questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
|
||||
v-model="questionForm[question.Id]"
|
||||
:title="questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] ? `${questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id].SegmentationName}\n${questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id].SegmentName}\n${questionForm[question.Id]}` : question.Remark"
|
||||
:disabled="(questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id] && question.ImageMarkEnum === 2) || question.ImageMarkEnum === 1 || question.IsPreinstall"
|
||||
style="width: 150px;margin-right: 5px;">
|
||||
<template v-if="question.Unit !== 0" slot="append">
|
||||
{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}
|
||||
</template>
|
||||
</el-input>
|
||||
<svg-icon v-if="question.ShowChartTypeEnum > 0 && taskInfo.IsReadingTaskViewInOrder === 1"
|
||||
icon-class="readingChart" class="svg-icon svg-readingChart" @click.stop="(e) => handleReadingChart({
|
||||
e,
|
||||
data: {
|
||||
TableQuestionId: question.Id,
|
||||
RowIndex: questionForm.RowIndex,
|
||||
QuestionName: question.QuestionName
|
||||
}
|
||||
})" />
|
||||
<!-- 绑定 -->
|
||||
<el-button
|
||||
v-if="readingTaskState < 2 && (!questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id])"
|
||||
size="mini" type="text" @click="operateImageMarker({ operateStateEnum: 21, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:bind') }}
|
||||
</el-button>
|
||||
<!-- 更改 -->
|
||||
<el-button
|
||||
v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id])"
|
||||
size="mini" type="text" @click="operateImageMarker({ operateStateEnum: 22, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:edit') }}
|
||||
</el-button>
|
||||
<!-- 查看 -->
|
||||
<el-button v-if="questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id]" size="mini"
|
||||
type="text" @click="operateImageMarker({ operateStateEnum: 23, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:view') }}
|
||||
</el-button>
|
||||
<!-- 移除 -->
|
||||
<el-button
|
||||
v-if="readingTaskState < 2 && (questionsSegmentMarkStatus[rowId ? `${rowId}_${question.Id}` : question.Id])"
|
||||
size="mini" type="text" @click="operateImageMarker({ operateStateEnum: 24, question })">
|
||||
{{ $t('dicom3D:CustomizeQuestionFormItem:button:remove') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div style="display: flex;flex-direction: row;justify-content: flex-start;align-items: center;" v-else>
|
||||
<el-input type="text" @change="(val) => { formItemNumberChange(val, question) }"
|
||||
@input="numberInput(question.Id)"
|
||||
@blur="questionsMarkStatus[question.Id] && questionsMarkStatus[question.Id].isMarked ? () => { } : handleMarkedQsBlur(questionForm[question.Id], questionForm, question.Id, question)"
|
||||
|
|
@ -276,6 +322,12 @@ export default {
|
|||
return {}
|
||||
}
|
||||
},
|
||||
questionsSegmentMarkStatus: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
parentQsId: {
|
||||
type: String,
|
||||
default: ''
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import setPetTransferFunctionForVolumeActor from "./setPetTransferFunctionForVolumeActor";
|
||||
import setMipTransferFunctionForVolumeActor from "./setMipTransferFunctionForVolumeActor";
|
||||
|
||||
|
||||
export {
|
||||
setPetTransferFunctionForVolumeActor,
|
||||
setMipTransferFunctionForVolumeActor
|
||||
};
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { cache, metaData, utilities } from "@cornerstonejs/core";
|
||||
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
|
||||
|
||||
export default function setMipTransferFunctionForVolumeActor({
|
||||
volumeActor,
|
||||
volumeId,
|
||||
}) {
|
||||
const mapper = volumeActor.getMapper?.();
|
||||
if (mapper?.setSampleDistance) {
|
||||
mapper.setSampleDistance(1.0);
|
||||
}
|
||||
const rgbTransferFunction = volumeActor
|
||||
.getProperty()
|
||||
.getRGBTransferFunction(0);
|
||||
let range = null;
|
||||
const imageVolume = volumeId ? cache.getVolume(volumeId) : null;
|
||||
const imageId = imageVolume?.imageIds?.[0];
|
||||
if (imageId) {
|
||||
const voiLutModule = metaData.get("voiLutModule", imageId);
|
||||
const rawCenter = Array.isArray(voiLutModule?.windowCenter)
|
||||
? voiLutModule.windowCenter[0]
|
||||
: voiLutModule?.windowCenter;
|
||||
const rawWidth = Array.isArray(voiLutModule?.windowWidth)
|
||||
? voiLutModule.windowWidth[0]
|
||||
: voiLutModule?.windowWidth;
|
||||
const center = Number(rawCenter);
|
||||
const width = Number(rawWidth);
|
||||
if (Number.isFinite(center) && Number.isFinite(width) && width > 0) {
|
||||
const upper = center + width / 2;
|
||||
range = [0, upper];
|
||||
} else if (Number.isFinite(center)) {
|
||||
range = [0, center];
|
||||
}
|
||||
}
|
||||
if (!range) {
|
||||
range = [0, 5];
|
||||
}
|
||||
|
||||
rgbTransferFunction.setRange(range[0], range[1]);
|
||||
utilities.invertRgbTransferFunction(rgbTransferFunction);
|
||||
const upper = Number(range[1]);
|
||||
if (Number.isFinite(upper) && upper > 0) {
|
||||
const ofun = vtkPiecewiseFunction.newInstance();
|
||||
ofun.addPoint(0, 0.0);
|
||||
ofun.addPoint(upper * 0.02, 0.9);
|
||||
ofun.addPoint(upper, 1.0);
|
||||
volumeActor.getProperty().setScalarOpacity(0, ofun);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import vtkColorTransferFunction from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction";
|
||||
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
|
||||
import { cache, metaData, utilities } from "@cornerstonejs/core";
|
||||
|
||||
const { getColormap } = utilities.colormap;
|
||||
|
||||
function getWindowCenterFromVolumeId(volumeId) {
|
||||
if (!volumeId) return null;
|
||||
const imageVolume = cache.getVolume?.(volumeId);
|
||||
const imageId = imageVolume?.imageIds?.[0];
|
||||
if (!imageId) return null;
|
||||
const voiLutModule = metaData.get("voiLutModule", imageId);
|
||||
const rawCenter = Array.isArray(voiLutModule?.windowCenter)
|
||||
? voiLutModule.windowCenter[0]
|
||||
: voiLutModule?.windowCenter;
|
||||
const center = Number(rawCenter);
|
||||
return Number.isFinite(center) ? center : null;
|
||||
}
|
||||
|
||||
export default function setPetColorMapTransferFunctionForVolumeActor({
|
||||
volumeActor,
|
||||
volumeId,
|
||||
preset,
|
||||
}) {
|
||||
const mapper = volumeActor.getMapper?.();
|
||||
if (mapper?.setSampleDistance) {
|
||||
mapper.setSampleDistance(1.0);
|
||||
}
|
||||
|
||||
const cfun = vtkColorTransferFunction.newInstance();
|
||||
const presetToUse = preset || getColormap("siemens");
|
||||
cfun.applyColorMap(presetToUse);
|
||||
|
||||
const center = getWindowCenterFromVolumeId(volumeId);
|
||||
const upper = center > 1 ? center : 5;
|
||||
cfun.setMappingRange(1, upper);
|
||||
|
||||
volumeActor.getProperty().setRGBTransferFunction(0, cfun);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { utilities } from '@cornerstonejs/core';
|
||||
|
||||
export default function setPetTransferFunction({ volumeActor }) {
|
||||
const rgbTransferFunction = volumeActor
|
||||
.getProperty()
|
||||
.getRGBTransferFunction(0);
|
||||
|
||||
rgbTransferFunction.setRange(0, 5);
|
||||
|
||||
utilities.invertRgbTransferFunction(rgbTransferFunction);
|
||||
}
|
||||
|
|
@ -2070,12 +2070,12 @@ export default {
|
|||
.merge-table {
|
||||
padding: 0 10px;
|
||||
|
||||
::v-deep.el-table {
|
||||
::v-deep .el-table {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
::v-deep.el-table td.el-table__cell,
|
||||
::v-deep .el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell.is-leaf {
|
||||
border-bottom: 1px solid #383838;
|
||||
}
|
||||
|
|
@ -2086,7 +2086,7 @@ export default {
|
|||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
::v-deep.el-table__header-wrapper {
|
||||
::v-deep .el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -2094,7 +2094,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__body-wrapper {
|
||||
::v-deep .el-table__body-wrapper {
|
||||
tr {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #dfdfdf;
|
||||
|
|
@ -2105,7 +2105,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-table__empty-block {
|
||||
::v-deep .el-table__empty-block {
|
||||
background-color: #1e1e1e !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -386,6 +386,35 @@ const config = {
|
|||
'disabledReason': ''
|
||||
},
|
||||
],
|
||||
'customizeStandardsSegmentDicom': [
|
||||
// {
|
||||
// 'name': 'Contour分割',
|
||||
// 'icon': 'contour',
|
||||
// 'toolName': 'Contour',
|
||||
// 'props': ['length'],
|
||||
// 'i18nKey': 'trials:reading:button:Contour',
|
||||
// 'isDisabled': false,
|
||||
// 'disabledReason': ''
|
||||
// },
|
||||
{
|
||||
'name': 'Labelmap分割',
|
||||
'icon': 'labelmap',
|
||||
'toolName': 'Labelmap',
|
||||
'props': ['max', 'min', 'volume', 'count', 'mean', 'stdDev', 'length', 'width'],
|
||||
'i18nKey': 'trials:reading:button:Labelmap',
|
||||
'isDisabled': false,
|
||||
'disabledReason': ''
|
||||
},
|
||||
// {
|
||||
// 'name': 'Surface分割',
|
||||
// 'icon': 'surface',
|
||||
// 'toolName': 'Surface',
|
||||
// 'props': ['area', 'mean', 'max', 'stdDev'],
|
||||
// 'i18nKey': 'trials:reading:button:Surface',
|
||||
// 'isDisabled': false,
|
||||
// 'disabledReason': ''
|
||||
// },
|
||||
],
|
||||
}
|
||||
const getTools = (criterionType) => {
|
||||
const standard = config.standards.find(s => s.type === criterionType)
|
||||
|
|
@ -399,4 +428,8 @@ const getCustomizeStandardsNoneDicomTools = (toolNames) => {
|
|||
const filteredTools = config.customizeStandardsNoneDicom.filter(item => toolNames.includes(item.toolName))
|
||||
return filteredTools || []
|
||||
}
|
||||
export { config, getTools, getCustomizeStandardsTools, getCustomizeStandardsNoneDicomTools }
|
||||
const getCustomizeStandardsSegmentDicomTools = (toolNames) => {
|
||||
const filteredTools = config.customizeStandardsSegmentDicom.filter(item => toolNames.includes(item.toolName))
|
||||
return filteredTools || []
|
||||
}
|
||||
export { config, getTools, getCustomizeStandardsTools, getCustomizeStandardsNoneDicomTools, getCustomizeStandardsSegmentDicomTools }
|
||||
|
|
|
|||
|
|
@ -316,11 +316,13 @@ class ScaleOverlayTool extends AnnotationDisplayTool {
|
|||
width: canvas.width / window.devicePixelRatio || 1,
|
||||
height: canvas.height / window.devicePixelRatio || 1,
|
||||
};
|
||||
if(!annotation||!annotation.data) return false
|
||||
const topLeft = annotation.data.handles.points[0];
|
||||
const topRight = annotation.data.handles.points[1];
|
||||
const bottomLeft = annotation.data.handles.points[2];
|
||||
const bottomRight = annotation.data.handles.points[3];
|
||||
const pointSet1 = [topLeft, bottomLeft, topRight, bottomRight];
|
||||
if(!bottomLeft) return false
|
||||
const worldWidthViewport = vec3.distance(bottomLeft, bottomRight);
|
||||
const worldHeightViewport = vec3.distance(topLeft, bottomLeft);
|
||||
const hscaleBounds = this.computeScaleBounds(canvasSize, 0.05, 0.05, location);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,264 @@
|
|||
// import { getEnabledElement, utilities as csUtils, getEnabledElementByViewportId, utilities, } from '@cornerstonejs/core';
|
||||
// import { addAnnotation, getAllAnnotations, getAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
|
||||
// import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
|
||||
// import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
|
||||
// import { drawLine as drawLineSvg, drawHandles as drawHandlesSvg, } from '../../drawingSvg';
|
||||
// import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
||||
// import { hideElementCursor } from '../../cursors/elementCursor';
|
||||
// import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
||||
// import BidirectionalTool from '../annotation/BidirectionalTool';
|
||||
// import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/segmentationColor';
|
||||
|
||||
|
||||
import { utilities, getEnabledElementByViewportId, getEnabledElement } from '@cornerstonejs/core';
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools'
|
||||
const {
|
||||
annotation,
|
||||
drawing,
|
||||
utilities: TSUtilities,
|
||||
BidirectionalTool,
|
||||
cursors,
|
||||
segmentation
|
||||
} = cornerstoneTools
|
||||
const { addAnnotation, getAllAnnotations, getAnnotations, removeAnnotation } = annotation.state
|
||||
const { isAnnotationLocked } = annotation.locking
|
||||
const { isAnnotationVisible } = annotation.visibility
|
||||
const { drawLine: drawLineSvg, drawHandles: drawHandlesSvg } = drawing
|
||||
const { getViewportIdsWithToolToRender } = TSUtilities.viewportFilters
|
||||
const { triggerAnnotationRenderForViewportIds } = TSUtilities
|
||||
const { hideElementCursor } = cursors.elementCursor
|
||||
const { getSegmentIndexColor } = segmentation.config.color
|
||||
class SegmentBidirectionalTool extends BidirectionalTool {
|
||||
static { this.toolName = 'SegmentBidirectional'; }
|
||||
constructor(toolProps = {}) {
|
||||
super(toolProps);
|
||||
this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
|
||||
let renderStatus = true;
|
||||
const { viewport } = enabledElement;
|
||||
const { element } = viewport;
|
||||
const viewportId = viewport.id;
|
||||
let annotations = getAnnotations(this.getToolName(), element);
|
||||
if (!annotations?.length) {
|
||||
return renderStatus;
|
||||
}
|
||||
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
||||
if (!annotations?.length) {
|
||||
return renderStatus;
|
||||
}
|
||||
const targetId = this.getTargetId(viewport);
|
||||
const renderingEngine = viewport.getRenderingEngine();
|
||||
const styleSpecifier = {
|
||||
toolGroupId: this.toolGroupId,
|
||||
toolName: this.getToolName(),
|
||||
viewportId: enabledElement.viewport.id,
|
||||
};
|
||||
for (let i = 0; i < annotations.length; i++) {
|
||||
const annotation = annotations[i];
|
||||
const { annotationUID, data } = annotation;
|
||||
const { points, activeHandleIndex } = data.handles;
|
||||
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
||||
styleSpecifier.annotationUID = annotationUID;
|
||||
const { segmentIndex, segmentationId } = annotation.metadata;
|
||||
const { lineWidth, lineDash, shadow } = this.getAnnotationStyle({
|
||||
annotation,
|
||||
styleSpecifier,
|
||||
});
|
||||
const colorArray = getSegmentIndexColor(viewportId, segmentationId, segmentIndex) || [255, 255, 255];
|
||||
// console.log(viewportId, segmentationId, segmentIndex, 'colorArray')
|
||||
// console.log(colorArray, 'colorArray')
|
||||
const color = `rgb(${colorArray.slice(0, 3).join(',')})`;
|
||||
if (!data.cachedStats[targetId] ||
|
||||
data.cachedStats[targetId].unit == null) {
|
||||
data.cachedStats[targetId] = {
|
||||
length: null,
|
||||
width: null,
|
||||
unit: null,
|
||||
};
|
||||
this._calculateCachedStats(annotation, renderingEngine, enabledElement);
|
||||
}
|
||||
else if (annotation.invalidated) {
|
||||
this._throttledCalculateCachedStats(annotation, renderingEngine, enabledElement);
|
||||
}
|
||||
if (!viewport.getRenderingEngine()) {
|
||||
console.warn('Rendering Engine has been destroyed');
|
||||
return renderStatus;
|
||||
}
|
||||
let activeHandleCanvasCoords;
|
||||
if (!isAnnotationVisible(annotationUID)) {
|
||||
continue;
|
||||
}
|
||||
if (!isAnnotationLocked(annotationUID) &&
|
||||
!this.editData &&
|
||||
activeHandleIndex !== null) {
|
||||
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
|
||||
}
|
||||
if (activeHandleCanvasCoords) {
|
||||
const handleGroupUID = '0';
|
||||
drawHandlesSvg(svgDrawingHelper, annotationUID, handleGroupUID, activeHandleCanvasCoords, {
|
||||
color,
|
||||
});
|
||||
}
|
||||
const dataId1 = `${annotationUID}-line-1`;
|
||||
const dataId2 = `${annotationUID}-line-2`;
|
||||
const lineUID = '0';
|
||||
drawLineSvg(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates[0], canvasCoordinates[1], {
|
||||
color,
|
||||
lineWidth,
|
||||
lineDash,
|
||||
shadow,
|
||||
}, dataId1);
|
||||
const secondLineUID = '1';
|
||||
drawLineSvg(svgDrawingHelper, annotationUID, secondLineUID, canvasCoordinates[2], canvasCoordinates[3], {
|
||||
color,
|
||||
lineWidth,
|
||||
lineDash,
|
||||
shadow,
|
||||
}, dataId2);
|
||||
renderStatus = true;
|
||||
const textLines = this.configuration.getTextLines(data, targetId);
|
||||
if (!textLines || textLines.length === 0) {
|
||||
continue;
|
||||
}
|
||||
if (!this.renderLinkedTextBoxAnnotation({
|
||||
enabledElement,
|
||||
svgDrawingHelper,
|
||||
annotation,
|
||||
styleSpecifier,
|
||||
textLines,
|
||||
canvasCoordinates,
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return renderStatus;
|
||||
};
|
||||
}
|
||||
addNewAnnotation(evt) {
|
||||
const eventDetail = evt.detail;
|
||||
const { currentPoints, element } = eventDetail;
|
||||
const worldPos = currentPoints.world;
|
||||
const enabledElement = getEnabledElement(element);
|
||||
const { viewport } = enabledElement;
|
||||
this.isDrawing = true;
|
||||
const camera = viewport.getCamera();
|
||||
const { viewPlaneNormal, viewUp } = camera;
|
||||
const referencedImageId = this.getReferencedImageId(viewport, worldPos, viewPlaneNormal, viewUp);
|
||||
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
|
||||
const annotation = {
|
||||
highlighted: true,
|
||||
invalidated: true,
|
||||
metadata: {
|
||||
toolName: this.getToolName(),
|
||||
viewPlaneNormal: [...viewPlaneNormal],
|
||||
viewUp: [...viewUp],
|
||||
FrameOfReferenceUID,
|
||||
referencedImageId,
|
||||
...viewport.getViewReference({ points: [worldPos] }),
|
||||
},
|
||||
data: {
|
||||
handles: {
|
||||
points: [
|
||||
[...worldPos],
|
||||
[...worldPos],
|
||||
[...worldPos],
|
||||
[...worldPos],
|
||||
],
|
||||
textBox: {
|
||||
hasMoved: false,
|
||||
worldPosition: [0, 0, 0],
|
||||
worldBoundingBox: {
|
||||
topLeft: [0, 0, 0],
|
||||
topRight: [0, 0, 0],
|
||||
bottomLeft: [0, 0, 0],
|
||||
bottomRight: [0, 0, 0],
|
||||
},
|
||||
},
|
||||
activeHandleIndex: null,
|
||||
},
|
||||
label: '',
|
||||
cachedStats: {},
|
||||
},
|
||||
};
|
||||
addAnnotation(annotation, element);
|
||||
const viewportIdsToRender = getViewportIdsWithToolToRender(element, this.getToolName());
|
||||
this.editData = {
|
||||
annotation,
|
||||
viewportIdsToRender,
|
||||
handleIndex: 1,
|
||||
movingTextBox: false,
|
||||
newAnnotation: true,
|
||||
hasMoved: false,
|
||||
};
|
||||
this._activateDraw(element);
|
||||
hideElementCursor(element);
|
||||
evt.preventDefault();
|
||||
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
||||
return annotation;
|
||||
}
|
||||
static {
|
||||
this.hydrate = (viewportId, axis, options) => {
|
||||
const enabledElement = getEnabledElementByViewportId(viewportId);
|
||||
if (!enabledElement) {
|
||||
return;
|
||||
}
|
||||
const { viewport } = enabledElement;
|
||||
const existingAnnotations = getAllAnnotations();
|
||||
const toolAnnotations = existingAnnotations.filter((annotation) => annotation.metadata.toolName === 'SegmentBidirectional');
|
||||
const existingAnnotation = toolAnnotations.find((annotation) => {
|
||||
const { metadata } = annotation;
|
||||
if (metadata.segmentIndex === options?.segmentIndex &&
|
||||
metadata.segmentationId === options?.segmentationId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (existingAnnotation) {
|
||||
removeAnnotation(existingAnnotation.annotationUID);
|
||||
}
|
||||
const { FrameOfReferenceUID, referencedImageId, viewPlaneNormal, instance, } = this.hydrateBase(SegmentBidirectionalTool, enabledElement, axis[0], options);
|
||||
const [majorAxis, minorAxis] = axis;
|
||||
const [major0, major1] = majorAxis;
|
||||
const [minor0, minor1] = minorAxis;
|
||||
const points = [major0, major1, minor0, minor1];
|
||||
const { toolInstance, ...serializableOptions } = options || {};
|
||||
const annotation = {
|
||||
annotationUID: options?.annotationUID || utilities.uuidv4(),
|
||||
data: {
|
||||
handles: {
|
||||
points,
|
||||
activeHandleIndex: null,
|
||||
textBox: {
|
||||
hasMoved: false,
|
||||
worldPosition: [0, 0, 0],
|
||||
worldBoundingBox: {
|
||||
topLeft: [0, 0, 0],
|
||||
topRight: [0, 0, 0],
|
||||
bottomLeft: [0, 0, 0],
|
||||
bottomRight: [0, 0, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
cachedStats: {},
|
||||
},
|
||||
highlighted: false,
|
||||
autoGenerated: false,
|
||||
invalidated: false,
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
metadata: {
|
||||
segmentIndex: options?.segmentIndex,
|
||||
segmentationId: options?.segmentationId,
|
||||
toolName: instance.getToolName(),
|
||||
viewPlaneNormal,
|
||||
FrameOfReferenceUID,
|
||||
referencedImageId,
|
||||
...serializableOptions,
|
||||
},
|
||||
};
|
||||
addAnnotation(annotation, viewport.element);
|
||||
triggerAnnotationRenderForViewportIds([viewport.id]);
|
||||
return annotation;
|
||||
};
|
||||
}
|
||||
}
|
||||
export default SegmentBidirectionalTool;
|
||||
|
|
@ -317,7 +317,7 @@ export default {
|
|||
color: yellow;
|
||||
}
|
||||
}
|
||||
::v-deep.el-button--text{
|
||||
::v-deep .el-button--text{
|
||||
color: #d0d0d0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -494,7 +494,7 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.medical-feedback-ir{
|
||||
::v-deep.search {
|
||||
::v-deep .search {
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,24 +209,28 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
// 初始化聊天信息
|
||||
getMessageList() {
|
||||
this.loading = true
|
||||
var param = {
|
||||
taskMedicalReviewId: this.taskMedicalReviewId
|
||||
}
|
||||
getMedicalReviewDialog(param).then(res => {
|
||||
async getMessageList() {
|
||||
try {
|
||||
this.loading = true
|
||||
var param = {
|
||||
taskMedicalReviewId: this.taskMedicalReviewId
|
||||
}
|
||||
let res = await getMedicalReviewDialog(param)
|
||||
this.otherInfo = res.OtherInfo
|
||||
this.recordContent = res.Result
|
||||
this.setScrollHeight()
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
setScrollHeight() {
|
||||
setTimeout(() => {
|
||||
var container = document.querySelectorAll('.chat-content')[0]
|
||||
container.scrollTop = container.scrollHeight
|
||||
if (container && container.scrollHeight) {
|
||||
container.scrollTop = container.scrollHeight
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
// 回复质疑
|
||||
|
|
@ -366,7 +370,7 @@ export default {
|
|||
color: yellow;
|
||||
}
|
||||
}
|
||||
::v-deep.el-button--text{
|
||||
::v-deep .el-button--text{
|
||||
color: #d0d0d0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,26 +80,32 @@ export default {
|
|||
handleClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
handleSave() {
|
||||
this.$refs['closeQCForm'].validate((valid) => {
|
||||
if (!valid) return
|
||||
this.loading = true
|
||||
this.form.TaskMedicalReviewId = this.taskMedicalReviewId
|
||||
this.form.IsClosedDialog = true
|
||||
closedMedicalReviewDialog(this.form)
|
||||
.then(res => {
|
||||
this.loading = false
|
||||
if (res.IsSuccess) {
|
||||
// 关闭成功!
|
||||
this.$message.success(this.$t('trials:qcQuality:message:closedSuccessfully'))
|
||||
this.$emit('close')
|
||||
this.$emit('refresh')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
async handleSave() {
|
||||
let validate = await this.$refs['closeQCForm'].validate()
|
||||
if (!validate) return
|
||||
this.form.TaskMedicalReviewId = this.taskMedicalReviewId
|
||||
this.form.IsClosedDialog = true
|
||||
this.$emit('closeAndSign', this.form)
|
||||
},
|
||||
// handleSave() {
|
||||
// this.$refs['closeQCForm'].validate((valid) => {
|
||||
// if (!valid) return
|
||||
// this.loading = true
|
||||
// this.form.TaskMedicalReviewId = this.taskMedicalReviewId
|
||||
// this.form.IsClosedDialog = true
|
||||
// closedMedicalReviewDialog(this.form)
|
||||
// .then(res => {
|
||||
// this.loading = false
|
||||
// if (res.IsSuccess) {
|
||||
// // 关闭成功!
|
||||
// this.$message.success(this.$t('trials:qcQuality:message:closedSuccessfully'))
|
||||
// this.$emit('refresh')
|
||||
// }
|
||||
// }).catch(() => {
|
||||
// this.loading = false
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -161,16 +161,16 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.issues-form{
|
||||
::v-deep.el-radio-group{
|
||||
::v-deep .el-radio-group{
|
||||
width:300px;
|
||||
}
|
||||
::v-deep.el-textarea{
|
||||
::v-deep .el-textarea{
|
||||
width:300px;
|
||||
}
|
||||
::v-deep.el-input{
|
||||
::v-deep .el-input{
|
||||
width:300px;
|
||||
}
|
||||
::v-deep.el-select{
|
||||
::v-deep .el-select{
|
||||
width:300px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -553,8 +553,8 @@
|
|||
>
|
||||
<CloseQC
|
||||
:task-medical-review-id="currentRow.Id"
|
||||
@closeAndSign="closeAndSign"
|
||||
@close="closeQuestionVisible = false"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
</el-dialog>
|
||||
<!--签名框 -->
|
||||
|
|
@ -645,7 +645,8 @@ export default {
|
|||
signCode: null,
|
||||
currentUser: zzSessionStorage.getItem('userName'),
|
||||
signVisible: false,
|
||||
timeList: []
|
||||
timeList: [],
|
||||
closeObj: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -697,6 +698,12 @@ export default {
|
|||
this.TrialReadingCriterionId = this.trialCriterionList[0].TrialReadingCriterionId
|
||||
}).catch(() => {})
|
||||
},
|
||||
closeAndSign(obj) {
|
||||
this.closeObj = Object.assign({}, obj)
|
||||
const { MedicalAudit } = const_.processSignature
|
||||
this.signCode = MedicalAudit
|
||||
this.signVisible = true
|
||||
},
|
||||
// 关闭签名框并设置确认状态
|
||||
closeSignDialog(isSign, signInfo) {
|
||||
if (isSign) {
|
||||
|
|
@ -706,38 +713,39 @@ export default {
|
|||
}
|
||||
},
|
||||
// 签名确认
|
||||
signConfirm(signInfo) {
|
||||
this.loading = true
|
||||
const params = {
|
||||
data: {
|
||||
taskMedicalReviewId: this.currentRow.Id
|
||||
},
|
||||
signInfo: signInfo
|
||||
}
|
||||
FinishMedicalReview(params).then(res => {
|
||||
async signConfirm(signInfo) {
|
||||
try {
|
||||
this.loading = true
|
||||
const params = {
|
||||
data: {
|
||||
taskMedicalReviewId: this.currentRow.Id,
|
||||
isClosedDialog: this.closeObj.IsClosedDialog,
|
||||
medicalDialogCloseEnum: this.closeObj.MedicalDialogCloseEnum,
|
||||
dialogCloseReason: this.closeObj.DialogCloseReason,
|
||||
},
|
||||
signInfo: signInfo
|
||||
}
|
||||
let res = await FinishMedicalReview(params)
|
||||
if (res.IsSuccess) {
|
||||
await this.$refs['chatForm'].getMessageList()
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
this.$refs['signForm'].btnLoading = false
|
||||
this.signVisible = false
|
||||
this.$nextTick(() => {
|
||||
this.closeQuestionVisible = false
|
||||
this.chatForm.visible = false
|
||||
})
|
||||
await this.getList()
|
||||
this.$emit('nextTask', this.taskMedicalReviewId)
|
||||
}
|
||||
this.loading = false
|
||||
this.getList()
|
||||
.then(() => {
|
||||
this.loading = true
|
||||
this.$emit('nextTask', this.taskMedicalReviewId)
|
||||
})
|
||||
.catch(action => {
|
||||
|
||||
})
|
||||
}).catch(_ => {
|
||||
} catch(e) {
|
||||
this.loading = false
|
||||
if (this.$refs['signForm']) {
|
||||
this.$refs['signForm'].btnLoading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
changeTimeList() {
|
||||
if (this.timeList) {
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ export default {
|
|||
height: 100%;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
::v-deep.el-tabs{
|
||||
::v-deep .el-tabs{
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
|
@ -324,12 +324,12 @@ export default {
|
|||
|
||||
}
|
||||
}
|
||||
::v-deep.dialog-container{
|
||||
::v-deep .dialog-container{
|
||||
margin-top: 50px !important;
|
||||
width:75%;
|
||||
height:80%;
|
||||
}
|
||||
::v-deep.el-dialog__body{
|
||||
::v-deep .el-dialog__body{
|
||||
padding: 20px 20px 0 20px;
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
|
@ -339,7 +339,7 @@ export default {
|
|||
}
|
||||
|
||||
.full-dialog-container{
|
||||
::v-deep.is-fullscreen .el-dialog__body{
|
||||
::v-deep .is-fullscreen .el-dialog__body{
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -884,7 +884,7 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.read-task-allocation {
|
||||
::v-deep.search {
|
||||
::v-deep .search {
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ export default {
|
|||
height: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
::v-deep.search {
|
||||
::v-deep .search {
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,12 +323,8 @@ export default {
|
|||
)
|
||||
var token = getToken()
|
||||
var path = ''
|
||||
if (this.readingTool === 0 || this.readingTool === 2) {
|
||||
if (this.criterionType === 0 && this.trialId === '08dd28b3-6843-fc05-0242-ac1301000000') {
|
||||
path = `/fusion?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&studyId=62b3dfc4-1e04-4180-910d-fe595f398361&ctseriesId=1bd24f53-d419-32e5-92d4-2b04640aaa65&ptseriesId=2b7b128d-8c3f-8357-ad14-e38f3acbbdff&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&TokenKey=${token}&lang=${this.$i18n.locale}`
|
||||
} else {
|
||||
path = `/readingDicoms?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
|
||||
}
|
||||
if (this.readingTool === 0 || this.readingTool === 2 || this.readingTool === 3) {
|
||||
path = `/readingDicoms?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
|
||||
} else {
|
||||
path = `/noneDicomReading?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
|
||||
}
|
||||
|
|
@ -408,7 +404,7 @@ export default {
|
|||
height: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
::v-deep.search {
|
||||
::v-deep .search {
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -466,7 +466,7 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.reread-task{
|
||||
::v-deep.search {
|
||||
::v-deep .search {
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ export default {
|
|||
border: 1px solid #607d8b !important;
|
||||
}
|
||||
|
||||
::v-deep.el-progress__text {
|
||||
::v-deep .el-progress__text {
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -295,7 +295,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep.el-collapse {
|
||||
::v-deep .el-collapse {
|
||||
border: none;
|
||||
|
||||
.el-collapse-item {
|
||||
|
|
|
|||
|
|
@ -221,12 +221,12 @@ export default {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
::v-deep.el-collapse-item__header {
|
||||
::v-deep .el-collapse-item__header {
|
||||
background: #e5ecef;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
::v-deep.el-collapse-item__content {
|
||||
::v-deep .el-collapse-item__content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -562,7 +562,7 @@ export default {
|
|||
background: #fff;
|
||||
padding: 0px 10px;
|
||||
box-sizing: border-box;
|
||||
::v-deep.search {
|
||||
::v-deep .search {
|
||||
padding: 0px !important;
|
||||
}
|
||||
::v-deep .el-tabs--border-card>.el-tabs__content {
|
||||
|
|
|
|||
|
|
@ -484,8 +484,15 @@
|
|||
:disabled="form.IsRequired === 2 && item.value === 1">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量工具 ImageTool -->
|
||||
<!--标记类型-->
|
||||
<el-form-item v-if="form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageMarkTypeEnum')" prop="ImageMarkTypeEnum">
|
||||
<el-radio-group v-model="form.ImageMarkTypeEnum" @change="ImageMarkTypeEnumChange">
|
||||
<el-radio v-for="item of $d.ImageMarkType" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量工具 ImageTool -->
|
||||
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 0"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
|
||||
{ required: true, message: this.$t('common:ruleMessage:select') }
|
||||
]">
|
||||
|
|
@ -495,6 +502,17 @@
|
|||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 分割工具 ImageTool -->
|
||||
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 1"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
|
||||
{ required: true, message: this.$t('common:ruleMessage:select') }
|
||||
]">
|
||||
<el-radio-group v-model="form.ImageTool" @change="imageSegmentToolChange">
|
||||
<el-radio v-for="tool of readingSegmentTools" :key="tool.toolName" :label="tool.toolName">
|
||||
{{ $t(tool.i18nKey) }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量值 ImageToolAttribute -->
|
||||
<el-form-item v-if="form.ImageTool && imageToolAttributes.length > 0"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageToolAttribute')" prop="ImageToolAttribute" :rules="[
|
||||
|
|
@ -696,6 +714,12 @@ export default {
|
|||
return []
|
||||
}
|
||||
},
|
||||
readingSegmentTools: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
readingVersionEnum: {
|
||||
type: Number,
|
||||
default: 0
|
||||
|
|
@ -754,6 +778,7 @@ export default {
|
|||
ClassifyEditType: null,
|
||||
ClassifyShowType: null,
|
||||
ImageMarkEnum: 0,
|
||||
ImageMarkTypeEnum: 0,
|
||||
ShowChartTypeEnum: 0,
|
||||
ImageTool: '',
|
||||
ImageToolAttribute: '',
|
||||
|
|
@ -1050,9 +1075,12 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (this.form.ImageTool) {
|
||||
if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 0) {
|
||||
this.imageToolChange(this.form.ImageTool)
|
||||
}
|
||||
if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 1) {
|
||||
this.imageSegmentToolChange(this.form.ImageTool)
|
||||
}
|
||||
}
|
||||
if (!this.data.ShowOrder && this.data.ShowOrder !== 0) {
|
||||
if (this.list.length > 0) {
|
||||
|
|
@ -1172,14 +1200,26 @@ export default {
|
|||
this.form.ImageTool = ''
|
||||
this.form.ImageToolAttribute = ''
|
||||
this.imageToolAttributes = []
|
||||
this.form.ImageMarkTypeEnum = 0
|
||||
}
|
||||
},
|
||||
ImageMarkTypeEnumChange(val) {
|
||||
this.form.ImageTool = ''
|
||||
this.form.ImageToolAttribute = ''
|
||||
this.imageToolAttributes = []
|
||||
},
|
||||
imageToolChange(v) {
|
||||
let i = this.readingTools.findIndex(tool => tool.toolName === v)
|
||||
if (i > -1) {
|
||||
this.imageToolAttributes = this.readingTools[i].props
|
||||
}
|
||||
},
|
||||
imageSegmentToolChange(v) {
|
||||
let i = this.readingSegmentTools.findIndex(tool => tool.toolName === v)
|
||||
if (i > -1) {
|
||||
this.imageToolAttributes = this.readingSegmentTools[i].props
|
||||
}
|
||||
},
|
||||
parentQuestionChange(val, form) {
|
||||
this.isParentExistGroup = false
|
||||
if (val) {
|
||||
|
|
@ -1273,6 +1313,7 @@ export default {
|
|||
form.ClassifyType = null
|
||||
form.ClassifyShowType = null
|
||||
form.ImageMarkEnum = 0
|
||||
form.ImageMarkTypeEnum = 0
|
||||
form.ShowChartTypeEnum = 0
|
||||
form.ImageTool = ''
|
||||
form.ImageToolAttribute = ''
|
||||
|
|
|
|||
|
|
@ -1,148 +1,81 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<div
|
||||
class="search-form"
|
||||
style="text-align: left;"
|
||||
>
|
||||
<div class="search-form" style="text-align: left;">
|
||||
<!-- 新增 -->
|
||||
<el-button
|
||||
v-if="!isConfirm && hasPermi(['trials:trials-panel:setting:reading-unit:edit']) && !isFromSystem"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<el-button v-if="!isConfirm && hasPermi(['trials:trials-panel:setting:reading-unit:edit']) && !isFromSystem"
|
||||
size="mini" type="primary" @click="handleAdd">
|
||||
{{ $t('common:button:add') }}
|
||||
</el-button>
|
||||
<!-- 预览 -->
|
||||
<el-button
|
||||
v-if="isPreview"
|
||||
:disabled="tblList.length===0"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handlePreview"
|
||||
>
|
||||
<el-button v-if="isPreview" :disabled="tblList.length === 0" size="mini" type="primary" @click="handlePreview">
|
||||
{{ $t('common:button:preview') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="tblList"
|
||||
size="small"
|
||||
>
|
||||
<el-table-column
|
||||
prop="ShowOrder"
|
||||
label=""
|
||||
width="50"
|
||||
/>
|
||||
<el-table :data="tblList" size="small">
|
||||
<el-table-column prop="ShowOrder" label="" width="50" />
|
||||
<!-- 分组 -->
|
||||
<el-table-column
|
||||
prop="QuestionGroupName"
|
||||
v-if="$i18n.locale === 'zh'"
|
||||
:label="$t('trials:readingUnit:qsList:title:groupNameEn')"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="QuestionGroupName" v-if="$i18n.locale === 'zh'"
|
||||
:label="$t('trials:readingUnit:qsList:title:groupNameEn')" show-overflow-tooltip />
|
||||
<!-- 分组(EN) -->
|
||||
<el-table-column
|
||||
prop="QuestionGroupEnName"
|
||||
v-if="$i18n.locale === 'en'"
|
||||
:label="$t('trials:readingUnit:qsList:title:groupNameEn')"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="QuestionGroupEnName" v-if="$i18n.locale === 'en'"
|
||||
:label="$t('trials:readingUnit:qsList:title:groupNameEn')" show-overflow-tooltip />
|
||||
<!-- 名称 -->
|
||||
<el-table-column
|
||||
prop="QuestionName"
|
||||
v-if="$i18n.locale === 'zh'"
|
||||
:label="$t('trials:readingUnit:qsList:title:qsNameEn')"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="QuestionName" v-if="$i18n.locale === 'zh'"
|
||||
:label="$t('trials:readingUnit:qsList:title:qsNameEn')" show-overflow-tooltip />
|
||||
<!-- 名称(EN) -->
|
||||
<el-table-column
|
||||
prop="QuestionEnName"
|
||||
v-if="$i18n.locale === 'en'"
|
||||
:label="$t('trials:readingUnit:qsList:title:qsNameEn')"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="QuestionEnName" v-if="$i18n.locale === 'en'"
|
||||
:label="$t('trials:readingUnit:qsList:title:qsNameEn')" show-overflow-tooltip />
|
||||
<!-- 题型 -->
|
||||
<el-table-column
|
||||
prop="Type"
|
||||
:label="$t('trials:readingUnit:qsList:title:type')"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="Type" :label="$t('trials:readingUnit:qsList:title:type')" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('Criterion_Question_Type',scope.row.Type) }}
|
||||
{{ $fd('Criterion_Question_Type', scope.row.Type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 是否显示 -->
|
||||
<el-table-column
|
||||
prop="ShowQuestion"
|
||||
:label="$t('trials:readingUnit:qsList:title:isShow')"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="ShowQuestion" :label="$t('trials:readingUnit:qsList:title:isShow')" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('ShowQuestion',scope.row.ShowQuestion) }}
|
||||
{{ $fd('ShowQuestion', scope.row.ShowQuestion) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 是否必填 -->
|
||||
<el-table-column
|
||||
prop="IsRequired"
|
||||
:label="$t('trials:readingUnit:qsList:title:isRequired')"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="IsRequired" :label="$t('trials:readingUnit:qsList:title:isRequired')"
|
||||
show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('QuestionRequired',scope.row.IsRequired) }}
|
||||
{{ $fd('QuestionRequired', scope.row.IsRequired) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 是否裁判问题 -->
|
||||
<el-table-column
|
||||
prop="IsJudgeQuestion"
|
||||
:label="$t('trials:readingUnit:qsList:title:isJudgeQuestion')"
|
||||
width="120"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="IsJudgeQuestion" :label="$t('trials:readingUnit:qsList:title:isJudgeQuestion')" width="120"
|
||||
show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('YesOrNo', scope.row.IsJudgeQuestion) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 是否在阅片页面显示 -->
|
||||
<el-table-column
|
||||
prop="IsShowInDicom"
|
||||
:label="$t('trials:readingUnit:qsList:title:isShowInDicom')"
|
||||
width="140"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="IsShowInDicom" :label="$t('trials:readingUnit:qsList:title:isShowInDicom')" width="140"
|
||||
show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('YesOrNo', scope.row.IsShowInDicom) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 是否在全局阅片显示 -->
|
||||
<el-table-column
|
||||
prop="GlobalReadingShowType"
|
||||
:label="$t('trials:readingUnit:qsList:title:globalReadingShowType')"
|
||||
width="160"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="GlobalReadingShowType" :label="$t('trials:readingUnit:qsList:title:globalReadingShowType')"
|
||||
width="160" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('GlobalReadingShowType', scope.row.GlobalReadingShowType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 导出目标表格 -->
|
||||
<el-table-column
|
||||
prop="ExportResult"
|
||||
:label="$t('trials:readingUnit:qsList:title:ExportResult')"
|
||||
width="160"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="ExportResult" :label="$t('trials:readingUnit:qsList:title:ExportResult')" width="160"
|
||||
show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ getStringResult(scope.row.ExportResult, 'ExportResult') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 限制编辑 -->
|
||||
<el-table-column
|
||||
prop="LimitEdit"
|
||||
:label="$t('trials:readingUnit:qsList:title:limitEdit')"
|
||||
width="160"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="LimitEdit" :label="$t('trials:readingUnit:qsList:title:limitEdit')" width="160"
|
||||
show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ $fd('LimitEdit', scope.row.LimitEdit) }}
|
||||
</template>
|
||||
|
|
@ -154,105 +87,50 @@
|
|||
width="140"
|
||||
show-overflow-tooltip
|
||||
/> -->
|
||||
<el-table-column
|
||||
prop=""
|
||||
:label="$t('common:action:action')"
|
||||
width="300"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column prop="" :label="$t('common:action:action')" width="300" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<!-- 编辑 -->
|
||||
<el-button
|
||||
v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
|
||||
type="primary"
|
||||
size="mini"
|
||||
@click="handleEdit(scope.row)"
|
||||
>
|
||||
<el-button v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
|
||||
type="primary" size="mini" @click="handleEdit(scope.row)">
|
||||
{{ $t('trials:readingUnit:qsList:title:edit') }}
|
||||
</el-button>
|
||||
<!-- 查看 -->
|
||||
<el-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="mini"
|
||||
@click="handlelook(scope.row)"
|
||||
>
|
||||
<el-button v-else type="primary" size="mini" @click="handlelook(scope.row)">
|
||||
{{ $t('trials:readingUnit:qsList:title:view') }}
|
||||
</el-button>
|
||||
<!-- 表格问题 -->
|
||||
<el-button
|
||||
v-if="scope.row.Type === 'table' || scope.row.Type === 'basicTable'"
|
||||
type="primary"
|
||||
size="mini"
|
||||
@click="handleConfig(scope.row)"
|
||||
>
|
||||
<el-button v-if="scope.row.Type === 'table' || scope.row.Type === 'basicTable'" type="primary" size="mini"
|
||||
@click="handleConfig(scope.row)">
|
||||
{{ $t('trials:readingUnit:qsList:title:tableQs') }}
|
||||
</el-button>
|
||||
<!-- 删除 -->
|
||||
<el-button
|
||||
v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
|
||||
type="danger"
|
||||
size="mini"
|
||||
@click="handleDelete(scope.row)"
|
||||
>
|
||||
<el-button v-if="!isConfirm && !isFromSystem && hasPermi(['trials:trials-panel:setting:reading-unit:edit'])"
|
||||
type="danger" size="mini" @click="handleDelete(scope.row)">
|
||||
{{ $t('trials:readingUnit:qsList:title:delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog
|
||||
v-if="addOrEdit.visible"
|
||||
:visible.sync="addOrEdit.visible"
|
||||
:close-on-click-modal="false"
|
||||
:title="addOrEdit.title"
|
||||
width="800px"
|
||||
append-to-body
|
||||
custom-class="base-dialog-wrapper"
|
||||
>
|
||||
<questions-form
|
||||
ref="addOrEdit"
|
||||
:data="rowData"
|
||||
:trial-criterion-id="trialCriterionId"
|
||||
:is-from-system="isFromSystem"
|
||||
:digit-places="digitPlaces"
|
||||
:readingTools="readingTools"
|
||||
:readingVersionEnum="readingVersionEnum"
|
||||
:list="tblList"
|
||||
:is-look="isLook"
|
||||
:is-system-criterion="isSystemCriterion"
|
||||
@close="addOrEdit.visible = false"
|
||||
@getList="getList"
|
||||
@reloadArbitrationRules="reloadArbitrationRules"
|
||||
/>
|
||||
<el-dialog v-if="addOrEdit.visible" :visible.sync="addOrEdit.visible" :close-on-click-modal="false"
|
||||
:title="addOrEdit.title" width="800px" append-to-body custom-class="base-dialog-wrapper">
|
||||
<questions-form ref="addOrEdit" :data="rowData" :trial-criterion-id="trialCriterionId"
|
||||
:is-from-system="isFromSystem" :digit-places="digitPlaces" :readingTools="readingTools"
|
||||
:readingSegmentTools="readingSegmentTools" :readingVersionEnum="readingVersionEnum" :list="tblList"
|
||||
:is-look="isLook" :is-system-criterion="isSystemCriterion" @close="addOrEdit.visible = false" @getList="getList"
|
||||
@reloadArbitrationRules="reloadArbitrationRules" />
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-if="preview.visible"
|
||||
:visible.sync="preview.visible"
|
||||
:close-on-click-modal="false"
|
||||
:title="preview.title"
|
||||
:fullscreen="true"
|
||||
>
|
||||
<el-dialog v-if="preview.visible" :visible.sync="preview.visible" :close-on-click-modal="false"
|
||||
:title="preview.title" :fullscreen="true">
|
||||
<questions-preview :criterion-id="trialCriterionId" :is-system-criterion="isSystemCriterion" :form-type="1" />
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-if="config.visible"
|
||||
:visible.sync="config.visible"
|
||||
:close-on-click-modal="false"
|
||||
:title="config.title"
|
||||
:fullscreen="true"
|
||||
>
|
||||
<table-qs-list
|
||||
:digit-places="digitPlaces"
|
||||
:reading-question-id="rowData.Id"
|
||||
:is-from-system="isFromSystem"
|
||||
:is-confirm="isConfirm"
|
||||
:criterion-id="trialCriterionId"
|
||||
:readingTools="readingTools"
|
||||
:readingVersionEnum="readingVersionEnum"
|
||||
@close="config.visible = false"
|
||||
/>
|
||||
<el-dialog v-if="config.visible" :visible.sync="config.visible" :close-on-click-modal="false" :title="config.title"
|
||||
:fullscreen="true">
|
||||
<table-qs-list :digit-places="digitPlaces" :reading-question-id="rowData.Id" :is-from-system="isFromSystem"
|
||||
:is-confirm="isConfirm" :criterion-id="trialCriterionId" :readingTools="readingTools"
|
||||
:readingSegmentTools="readingSegmentTools" :readingVersionEnum="readingVersionEnum"
|
||||
@close="config.visible = false" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -310,6 +188,12 @@ export default {
|
|||
return []
|
||||
}
|
||||
},
|
||||
readingSegmentTools: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
readingVersionEnum: {
|
||||
type: Number,
|
||||
default: 0
|
||||
|
|
@ -350,7 +234,8 @@ export default {
|
|||
this.rowData = {
|
||||
ReadingQuestionCriterionTrialId: this.trialCriterionId,
|
||||
ReadingCriterionPageId: this.readingCriterionPageId,
|
||||
Id: '' }
|
||||
Id: ''
|
||||
}
|
||||
this.isLook = false
|
||||
this.addOrEdit.title = this.$t('trials:readingUnit:qsList:title:add')// '添加'
|
||||
this.addOrEdit.visible = true
|
||||
|
|
@ -362,9 +247,9 @@ export default {
|
|||
this.rowData.ReadingCriterionPageId = this.readingCriterionPageId
|
||||
let title = ''
|
||||
if (this.$i18n.locale === 'zh' && (row.QuestionName || row.GroupName)) {
|
||||
title =`:${row.QuestionName ? row.QuestionName : row.GroupName}`
|
||||
title = `:${row.QuestionName ? row.QuestionName : row.GroupName}`
|
||||
} else if (this.$i18n.locale === 'en' && (row.QuestionEnName || row.GroupEnName)) {
|
||||
title =`: ${row.QuestionEnName ? row.QuestionEnName : row.GroupEnName}`
|
||||
title = `: ${row.QuestionEnName ? row.QuestionEnName : row.GroupEnName}`
|
||||
}
|
||||
this.addOrEdit.title = `${this.$t('trials:readingUnit:qsList:title:view')} ${title} `// '查看'
|
||||
this.addOrEdit.visible = true
|
||||
|
|
@ -400,8 +285,8 @@ export default {
|
|||
// this.preview.visible = true
|
||||
window.localStorage.setItem('TrialReadingCriterionId', this.TrialReadingCriterionId)
|
||||
var token = getToken()
|
||||
var path = `/criterionquestions?TrialReadingCriterionId=${this.trialCriterionId}&isSystemCriterion=${this.isSystemCriterion}&TokenKey=${token}`
|
||||
|
||||
var path = `/criterionquestions?TrialReadingCriterionId=${this.trialCriterionId}&isSystemCriterion=${this.isSystemCriterion}&TokenKey=${token}`
|
||||
|
||||
var routeData = this.$router.resolve({ path })
|
||||
this.openWindow = window.open(routeData.href, '_blank')
|
||||
},
|
||||
|
|
@ -431,5 +316,4 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,59 +1,37 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form
|
||||
ref="readingCriterionsForm"
|
||||
v-loading="loading"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
size="small"
|
||||
>
|
||||
<el-form ref="readingCriterionsForm" v-loading="loading" :model="form" :rules="rules" label-width="120px"
|
||||
size="small">
|
||||
<!-- '表单问题' -->
|
||||
<el-form-item :label="$t('trials:readingUnit:readingCriterion:title:formQs')">
|
||||
<questions-list
|
||||
:ref="`questionList${trialReadingCriterionId}`"
|
||||
v-if="form.FormType===1"
|
||||
:trial-reading-criterion-id="trialReadingCriterionId"
|
||||
:list="readingInfo.TrialQuestionList"
|
||||
:trial-criterion-id="readingInfo.TrialCriterionId"
|
||||
:is-confirm="isConfirm"
|
||||
:is-system-criterion="isSystemCriterion"
|
||||
:is-from-system="readingInfo.IsFromSystem"
|
||||
:digit-places="digitPlaces"
|
||||
:readingTools="readingTools"
|
||||
:readingVersionEnum="readingVersionEnum"
|
||||
@reloadArbitrationRules="reloadArbitrationRules"
|
||||
/>
|
||||
<questions-list :ref="`questionList${trialReadingCriterionId}`" v-if="form.FormType === 1"
|
||||
:trial-reading-criterion-id="trialReadingCriterionId" :list="readingInfo.TrialQuestionList"
|
||||
:trial-criterion-id="readingInfo.TrialCriterionId" :is-confirm="isConfirm"
|
||||
:is-system-criterion="isSystemCriterion" :is-from-system="readingInfo.IsFromSystem"
|
||||
:digit-places="digitPlaces" :readingTools="readingTools" :readingSegmentTools="readingSegmentTools"
|
||||
:readingVersionEnum="readingVersionEnum" @reloadArbitrationRules="reloadArbitrationRules" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if=" hasPermi(['trials:trials-panel:setting:reading-unit:edit'])">
|
||||
<!-- <!– 保存 –>-->
|
||||
<!-- <el-button-->
|
||||
<!-- v-if="!isConfirm && isAdditionalAssessment"-->
|
||||
<!-- type="primary"-->
|
||||
<!-- @click="handleSave(true)"-->
|
||||
<!-- >-->
|
||||
<!-- {{ $t('common:button:save') }}-->
|
||||
<!-- </el-button>-->
|
||||
<el-form-item v-if="hasPermi(['trials:trials-panel:setting:reading-unit:edit'])">
|
||||
<!-- <!– 保存 –>-->
|
||||
<!-- <el-button-->
|
||||
<!-- v-if="!isConfirm && isAdditionalAssessment"-->
|
||||
<!-- type="primary"-->
|
||||
<!-- @click="handleSave(true)"-->
|
||||
<!-- >-->
|
||||
<!-- {{ $t('common:button:save') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- 基础数据配置 -->
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="configBaseDataVisible = true"
|
||||
>
|
||||
<el-button type="primary" @click="configBaseDataVisible = true">
|
||||
{{ $t('trials:readingUnit:readingCriterion:title:baseDataCfg') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 基础数据配置 -->
|
||||
<el-dialog
|
||||
v-if="configBaseDataVisible"
|
||||
:title="$t('trials:readingUnit:readingCriterion:title:baseDataCfg')"
|
||||
:visible.sync="configBaseDataVisible"
|
||||
:close-on-click-modal="false"
|
||||
:fullscreen="true"
|
||||
append-to-body
|
||||
custom-class="base-dialog-wrapper"
|
||||
>
|
||||
<BaseDataConfig :trial-reading-criterion-id="trialReadingCriterionId" :is-from-system="readingInfo.IsFromSystem" :is-confirm="isConfirm" />
|
||||
<el-dialog v-if="configBaseDataVisible" :title="$t('trials:readingUnit:readingCriterion:title:baseDataCfg')"
|
||||
:visible.sync="configBaseDataVisible" :close-on-click-modal="false" :fullscreen="true" append-to-body
|
||||
custom-class="base-dialog-wrapper">
|
||||
<BaseDataConfig :trial-reading-criterion-id="trialReadingCriterionId" :is-from-system="readingInfo.IsFromSystem"
|
||||
:is-confirm="isConfirm" />
|
||||
</el-dialog>
|
||||
|
||||
|
||||
|
|
@ -85,6 +63,12 @@ export default {
|
|||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
readingSegmentTools: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -122,7 +106,7 @@ export default {
|
|||
this.additionalAssessmentOptionList = res.Result
|
||||
if (this.additionalAssessmentOptionList.length > 0) {
|
||||
this.additionalAssessmentOptionList.forEach(v => {
|
||||
this.$set(this.form, 'AdditionalAssessmentType'+v.Id, v.IsSelected)
|
||||
this.$set(this.form, 'AdditionalAssessmentType' + v.Id, v.IsSelected)
|
||||
})
|
||||
}
|
||||
getTrialReadingCriterionInfo({ trialId, TrialReadingCriterionId: this.trialReadingCriterionId }).then(res => {
|
||||
|
|
@ -185,7 +169,7 @@ export default {
|
|||
reloadArbitrationRules() {
|
||||
this.$emit('reloadArbitrationRules')
|
||||
},
|
||||
handleConfig() {}
|
||||
handleConfig() { }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,8 @@
|
|||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量工具 -->
|
||||
<el-form-item
|
||||
v-if="CriterionType === 0 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2) && form.ReadingVersionEnum === 1"
|
||||
<!-- 测量工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
|
||||
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1"
|
||||
:label="$t('trials:readingUnit:readingRules:title:measureTool')">
|
||||
<el-checkbox-group v-model="form.ReadingToolList" :disabled="isConfirm ||
|
||||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
|
||||
|
|
@ -42,6 +41,17 @@
|
|||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<!--分割工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
|
||||
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
|
||||
:label="$t('trials:readingUnit:readingRules:title:segmentTool')">
|
||||
<el-checkbox-group v-model="form.SegmentToolList" :disabled="isConfirm ||
|
||||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
|
||||
">
|
||||
<el-checkbox v-for="tool in segmentTools" :key="tool.toolName" :label="tool.toolName" name="SegmentToolList">
|
||||
{{ $t(`${tool.i18nKey}`) }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<!--检查类型筛选-->
|
||||
<el-form-item :label="$t('trials:processCfg:form:IsImageFilter')" prop="IsImageFilter">
|
||||
<el-radio-group v-model="form.IsImageFilter" @input="IsImageFilterChange" :disabled="isConfirm ||
|
||||
|
|
@ -421,6 +431,7 @@ export default {
|
|||
ReadingTool: 0,
|
||||
ReadingVersionEnum: null,
|
||||
ReadingToolList: [],
|
||||
SegmentToolList: [],
|
||||
ReadingTaskViewEnum: null,
|
||||
IsImageLabeled: null,
|
||||
IsReadingShowSubjectInfo: null,
|
||||
|
|
@ -616,7 +627,8 @@ export default {
|
|||
modalityList: [],
|
||||
CriterionModalitys: [],
|
||||
modalityIsCheck: false, // 是否允许影像筛选
|
||||
tools: []
|
||||
tools: [],
|
||||
segmentTools: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -737,13 +749,20 @@ export default {
|
|||
// 阅片工具选择
|
||||
handleReadingToolInput(val) {
|
||||
this.form.ReadingToolList = []
|
||||
if (this.CriterionType !== 0) return this.tools = []
|
||||
if (val === 0 || val === 2) {
|
||||
this.form.SegmentToolList = []
|
||||
if (this.CriterionType !== 0) {
|
||||
this.segmentTools = []
|
||||
return this.tools = []
|
||||
}
|
||||
if (val === 0 || val === 2 || val === 3) {
|
||||
this.tools = [...config.customizeStandards]
|
||||
}
|
||||
if (val === 1) {
|
||||
this.tools = [...config.customizeStandardsNoneDicom]
|
||||
}
|
||||
if (val === 3) {
|
||||
this.segmentTools = [...config.customizeStandardsSegmentDicom]
|
||||
}
|
||||
},
|
||||
// 检查类型筛选值变更
|
||||
IsImageFilterChange(data) {
|
||||
|
|
@ -786,12 +805,15 @@ export default {
|
|||
this.form.KeyFileListStr = this.form.KeyFileList.map(item => item.FileName).join(',')
|
||||
}
|
||||
if (this.CriterionType === 0) {
|
||||
if (this.form.ReadingTool === 0 || this.form.ReadingTool === 2) {
|
||||
if (this.form.ReadingTool === 0 || this.form.ReadingTool === 2 || this.form.ReadingTool === 3) {
|
||||
this.tools = [...config.customizeStandards]
|
||||
}
|
||||
if (this.form.ReadingTool === 1) {
|
||||
this.tools = [...config.customizeStandardsNoneDicom]
|
||||
}
|
||||
if (this.form.ReadingTool === 3) {
|
||||
this.segmentTools = [...config.customizeStandardsSegmentDicom]
|
||||
}
|
||||
}
|
||||
this.CriterionModalitys = this.form.CriterionModalitys
|
||||
? this.form.CriterionModalitys.split('|')
|
||||
|
|
@ -817,7 +839,7 @@ export default {
|
|||
this.$emit('setGlobalReading', res.Result.IsGlobalReading)
|
||||
this.$emit('setOncologyReading', res.Result.IsOncologyReading)
|
||||
this.$emit('setDigitPlaces', res.Result.DigitPlaces)
|
||||
this.$emit('setReadingTools', { readingTools: res.Result.ReadingToolList, isNoneDicom: this.form.ReadingTool === 1 })
|
||||
this.$emit('setReadingTools', { readingTools: res.Result.ReadingToolList, readingSegmentTools: res.Result.SegmentToolList, ReadingTool: this.form.ReadingTool })
|
||||
|
||||
if (res.Result.ReadingType === 1) {
|
||||
this.$emit('setArbitrationReading', false)
|
||||
|
|
@ -859,7 +881,7 @@ export default {
|
|||
this.$emit('setGlobalReading', this.form.IsGlobalReading)
|
||||
this.$emit('setOncologyReading', this.form.IsOncologyReading)
|
||||
this.$emit('setDigitPlaces', this.form.DigitPlaces)
|
||||
this.$emit('setReadingTools', { readingTools: this.form.ReadingToolList, isNoneDicom: this.form.ReadingTool === 1 })
|
||||
this.$emit('setReadingTools', { readingTools: this.form.ReadingToolList, readingSegmentTools: this.form.SegmentToolList, ReadingTool: this.form.ReadingTool })
|
||||
if (this.form.ReadingType === 1) {
|
||||
this.$emit('setArbitrationReading', false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,8 +286,15 @@
|
|||
:disabled="form.IsRequired === 2 && item.value === 1">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量工具 ImageTool -->
|
||||
<!--标记类型-->
|
||||
<el-form-item v-if="form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageMarkTypeEnum')" prop="ImageMarkTypeEnum">
|
||||
<el-radio-group v-model="form.ImageMarkTypeEnum" @change="ImageMarkTypeEnumChange">
|
||||
<el-radio v-for="item of $d.ImageMarkType" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量工具 ImageTool -->
|
||||
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 0"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
|
||||
{ required: true, message: this.$t('common:ruleMessage:select') }
|
||||
]">
|
||||
|
|
@ -297,6 +304,17 @@
|
|||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 分割工具 ImageTool -->
|
||||
<el-form-item v-if="(form.ImageMarkEnum === 1 || form.ImageMarkEnum === 2) && form.ImageMarkTypeEnum === 1"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageTool')" prop="ImageTool" :rules="[
|
||||
{ required: true, message: this.$t('common:ruleMessage:select') }
|
||||
]">
|
||||
<el-radio-group v-model="form.ImageTool" @change="imageSegmentToolChange">
|
||||
<el-radio v-for="tool of readingSegmentTools" :key="tool.toolName" :label="tool.toolName">
|
||||
{{ $t(tool.i18nKey) }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 测量值 ImageToolAttribute -->
|
||||
<el-form-item v-if="form.ImageTool && imageToolAttributes.length > 0"
|
||||
:label="$t('trials:readingUnit:qsList:title:ImageToolAttribute')" prop="ImageToolAttribute" :rules="[
|
||||
|
|
@ -563,6 +581,12 @@ export default {
|
|||
return []
|
||||
}
|
||||
},
|
||||
readingSegmentTools: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
readingVersionEnum: {
|
||||
type: Number,
|
||||
default: 0
|
||||
|
|
@ -617,6 +641,7 @@ export default {
|
|||
ClassifyEditType: null,
|
||||
ClassifyShowType: null,
|
||||
ImageMarkEnum: 0,
|
||||
ImageMarkTypeEnum: 0,
|
||||
ShowChartTypeEnum: 0,
|
||||
ImageTool: '',
|
||||
ImageToolAttribute: '',
|
||||
|
|
@ -898,9 +923,12 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (this.form.ImageTool) {
|
||||
if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 0) {
|
||||
this.imageToolChange(this.form.ImageTool)
|
||||
}
|
||||
if (this.form.ImageTool && this.form.ImageMarkTypeEnum === 1) {
|
||||
this.imageSegmentToolChange(this.form.ImageTool)
|
||||
}
|
||||
}
|
||||
if (this.form.ClassifyTableQuestionId) {
|
||||
this.classifyQuestionChange(this.form.ClassifyTableQuestionId)
|
||||
|
|
@ -1011,14 +1039,26 @@ export default {
|
|||
this.form.ImageTool = ''
|
||||
this.form.ImageToolAttribute = ''
|
||||
this.imageToolAttributes = []
|
||||
this.form.ImageMarkTypeEnum = 0
|
||||
}
|
||||
},
|
||||
ImageMarkTypeEnumChange(val) {
|
||||
this.form.ImageTool = ''
|
||||
this.form.ImageToolAttribute = ''
|
||||
this.imageToolAttributes = []
|
||||
},
|
||||
imageToolChange(v) {
|
||||
let i = this.readingTools.findIndex(tool => tool.toolName === v)
|
||||
if (i > -1) {
|
||||
this.imageToolAttributes = this.readingTools[i].props
|
||||
}
|
||||
},
|
||||
imageSegmentToolChange(v) {
|
||||
let i = this.readingSegmentTools.findIndex(tool => tool.toolName === v)
|
||||
if (i > -1) {
|
||||
this.imageToolAttributes = this.readingSegmentTools[i].props
|
||||
}
|
||||
},
|
||||
async parentQuestionChange(val, form) {
|
||||
if (val) {
|
||||
var index = this.parentOptions.findIndex(item => {
|
||||
|
|
@ -1129,6 +1169,7 @@ export default {
|
|||
form.ClassifyType = null
|
||||
form.ClassifyShowType = null
|
||||
form.ImageMarkEnum = 0
|
||||
form.ImageMarkTypeEnum = 0
|
||||
form.ShowChartTypeEnum = 0
|
||||
form.ImageTool = ''
|
||||
form.ImageToolAttribute = ''
|
||||
|
|
|
|||