Merge branch 'main' into uat_us

# Conflicts:
#	src/views/trials/trials-layout/components/trialsNavbar.vue
uat_us
wangxiaoshuang 2026-04-01 16:18:38 +08:00
commit 0f19a612b7
108 changed files with 11867 additions and 11055 deletions

View File

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

10997
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
]
}
}

View File

@ -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: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

12
src/icons/svg/contour.svg Normal file
View File

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

1
src/icons/svg/fill.svg Normal file
View File

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

View File

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

1
src/icons/svg/jumpto.svg Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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',

View File

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

View File

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

View File

@ -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,
};

View File

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

View File

@ -0,0 +1,3 @@
export default function isNMReconstructable(imageSubType) {
return imageSubType === 'RECON TOMO' || imageSubType === 'RECON GATED TOMO';
}

View File

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

View File

@ -965,8 +965,8 @@ export default {
}
</script>
<style lang="scss" scoped>
.imageBox {
<style lang="scss">
.viewerContainer .imageBox {
position: relative;
i {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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%;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)}`
// PTMIP (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))
// PTMIP (CT)
if (this.index !== 1) {
this.$emit('upperRangeChange', Math.round(upper))
}
}
},
async getScreenshots() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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}`
}
},
}

View File

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

View File

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

View File

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

View File

@ -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: ''

View File

@ -0,0 +1,8 @@
import setPetTransferFunctionForVolumeActor from "./setPetTransferFunctionForVolumeActor";
import setMipTransferFunctionForVolumeActor from "./setMipTransferFunctionForVolumeActor";
export {
setPetTransferFunctionForVolumeActor,
setMipTransferFunctionForVolumeActor
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -317,7 +317,7 @@ export default {
color: yellow;
}
}
::v-deep.el-button--text{
::v-deep .el-button--text{
color: #d0d0d0;
}
}

View File

@ -494,7 +494,7 @@ export default {
<style lang="scss" scoped>
.medical-feedback-ir{
::v-deep.search {
::v-deep .search {
padding: 0px !important;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -884,7 +884,7 @@ export default {
</script>
<style lang="scss" scoped>
.read-task-allocation {
::v-deep.search {
::v-deep .search {
padding: 0px !important;
}
}

View File

@ -548,7 +548,7 @@ export default {
height: 100%;
background-color: #fff;
::v-deep.search {
::v-deep .search {
padding: 0px !important;
}
}

View File

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

View File

@ -466,7 +466,7 @@ export default {
</script>
<style lang="scss" scoped>
.reread-task{
::v-deep.search {
::v-deep .search {
padding: 0px !important;
}
}

View File

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

View File

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

View File

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

View File

@ -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 = ''

View File

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

View File

@ -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'])">
<!-- &lt;!&ndash; 保存 &ndash;&gt;-->
<!-- <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'])">
<!-- &lt;!&ndash; 保存 &ndash;&gt;-->
<!-- <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() { }
}
}

View File

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

View File

@ -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 = ''

Some files were not shown because too many files have changed in this diff Show More