Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
commit
72d0494baa
|
|
@ -127,7 +127,6 @@ export default {
|
|||
sliceThickness: null,
|
||||
wwwc: null,
|
||||
total: 0,
|
||||
sliceThickness: 0
|
||||
},
|
||||
digitPlaces: 2,
|
||||
orientationMarkers: [],
|
||||
|
|
@ -277,6 +276,7 @@ export default {
|
|||
let imageId = imageIds[0]
|
||||
let volume = cache.getVolume(this.volumeId)
|
||||
let spacing = volume ? volume.spacing : []
|
||||
console.log(spacing, 'spacing')
|
||||
// if (this.series.orientation === 'AXIAL') imageId = viewport.getCurrentImageId()
|
||||
if (imageId && volume) {
|
||||
this.$emit('setMPRInfo', { type: this.series.orientation, key: "imageNum", value: detail.numberOfSlices })
|
||||
|
|
@ -467,13 +467,13 @@ export default {
|
|||
this.loading = true
|
||||
try {
|
||||
return await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
async setSeriesInfo(obj, isLocate = false) {
|
||||
try {
|
||||
let data = obj
|
||||
|
|
@ -501,7 +501,8 @@ export default {
|
|||
DicomEvent.$emit("isloaded", {})
|
||||
}
|
||||
}]).then(r => {
|
||||
if (data.isLocation) {
|
||||
console.log(this.imageInfo.zoom, 'this.imageInfo.zoom')
|
||||
if (data.isLocation || !this.imageInfo.zoom) {
|
||||
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1401,7 +1401,9 @@ export default {
|
|||
toolGroup.addTool(WindowLevelTool.toolName, {
|
||||
targetViewportIds: volumeViewportIds
|
||||
})
|
||||
toolGroup.addTool(CrosshairsTool.toolName);
|
||||
toolGroup.addTool(CrosshairsTool.toolName, {
|
||||
getReferenceLineColor: this.setCrosshairsToolLineColor
|
||||
});
|
||||
} else {
|
||||
toolGroup.addTool(WindowLevelTool.toolName)
|
||||
}
|
||||
|
|
@ -2037,6 +2039,21 @@ export default {
|
|||
const viewport = renderingEngine.getViewport(viewportId)
|
||||
viewport.render()
|
||||
},
|
||||
setCrosshairsToolLineColor(viewportId) {
|
||||
let colors = [
|
||||
'#0ca8df',
|
||||
'#ffd10a',
|
||||
'#b6d634',
|
||||
'#3fbe95',
|
||||
'#785db0',
|
||||
'#5070dd',
|
||||
'#505372',
|
||||
'#ff994d',
|
||||
'#fb628b',
|
||||
]
|
||||
let index = viewportId.split("-").pop()
|
||||
return colors[colors.length - 1 - Number(index)] || colors[0]
|
||||
},
|
||||
getLengthToolTextLines(data, targetId) {
|
||||
const cachedVolumeStats = data.cachedStats[targetId]
|
||||
const { length, unit } = cachedVolumeStats
|
||||
|
|
@ -2519,6 +2536,9 @@ export default {
|
|||
viewport.resetProperties()
|
||||
viewport.render()
|
||||
renderingEngine.render()
|
||||
if (this.readingTool === 3) {
|
||||
DicomEvent.$emit('isloaded', {})
|
||||
}
|
||||
},
|
||||
// 更改视图布局
|
||||
async changeLayout(v) {
|
||||
|
|
@ -2797,6 +2817,18 @@ export default {
|
|||
toggleFullScreen(e, index) {
|
||||
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
|
||||
this.activeViewportIndex = index
|
||||
if (this.readingTool === 3 || this.isMPR) {
|
||||
// this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series)
|
||||
this.$nextTick(() => {
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
renderingEngine.resize(true, false)
|
||||
renderingEngine.render()
|
||||
if (this.readingTool === 3) {
|
||||
DicomEvent.$emit('isloaded', {})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
if (this.isFusion) {
|
||||
const viewportIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`]
|
||||
viewportIds.forEach(id => {
|
||||
|
|
@ -2899,7 +2931,16 @@ export default {
|
|||
this.activeViewportIndex = 0
|
||||
this.fullScreenIndex = null
|
||||
this.isMPR = false
|
||||
return this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
|
||||
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
|
||||
return this.$nextTick(() => {
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
renderingEngine.resize(true, false)
|
||||
renderingEngine.render()
|
||||
if (this.readingTool === 3) {
|
||||
DicomEvent.$emit('isloaded', {})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
if (!obj.IsDicom) {
|
||||
return this.previewNoneDicoms(obj)
|
||||
|
|
@ -3523,9 +3564,9 @@ export default {
|
|||
syncColormap: false
|
||||
})
|
||||
let viewportIds = [
|
||||
`viewport-volume-0`,
|
||||
`viewport-volume-1`,
|
||||
`viewport-volume-2`
|
||||
`viewport-MPR-0`,
|
||||
`viewport-MPR-1`,
|
||||
`viewport-MPR-2`
|
||||
]
|
||||
viewportIds.forEach((viewportId) => {
|
||||
MPRVoiSynchronizer.add({
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@
|
|||
</el-switch>
|
||||
<span style="margin-left: 5px;">{{
|
||||
$t('trials:reading:Segmentations:title:InactiveSegmentationsShow')
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<!-- <div class="SegmentConfig" v-if="SegmentConfig.InactiveSegmentations.show">
|
||||
<span>{{ $t('trials:reading:Segmentations:title:Opacity') }}</span>
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
<div v-for="k in statsKey" :key="k" class="statsBox">
|
||||
<span>{{ k }}</span>
|
||||
<span v-if="item.stats[k]">{{ Number(item.stats[k].value).toFixed(2)
|
||||
}}<i>{{ item.stats[k].unit }}</i></span>
|
||||
}}<i>{{ item.stats[k].unit }}</i></span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="serialNum" slot="reference">{{ index + 1 }}</div>
|
||||
|
|
@ -205,7 +205,9 @@
|
|||
<svg-icon :icon-class="item && !item.view ? 'eye' : 'eye-open'"
|
||||
@click.stop="viewSegment(item, !item.view)" class="docShow" />
|
||||
<i class="el-icon-lock" v-if="item.lock" @click.stop="lockSegment(item, false)"></i>
|
||||
<el-popover placement="bottom" width="40" trigger="click" class="docShow">
|
||||
<el-popover placement="bottom" width="40" trigger="click" class="docShow"
|
||||
:value="popoverId === `popover-${item.segmentationId}_${item.segmentIndex}`"
|
||||
@show="handleClickPopover(item)">
|
||||
<div class="SegmentGroupBtnBox">
|
||||
<div class="SegmentGroupBtn" @click.stop="rename('segment', item)">
|
||||
{{ $t('trials:reading:Segmentations:button:renameSegmentGroup') }}
|
||||
|
|
@ -232,7 +234,7 @@
|
|||
</el-collapse>
|
||||
<div class="saveBtnBox">
|
||||
<el-button type="success" size="small" @click="saveSegmentGroup()">{{ $t("common:button:save")
|
||||
}}</el-button>
|
||||
}}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -352,7 +354,8 @@ export default {
|
|||
drawing: false, // 是否正在分割
|
||||
isDel: false,
|
||||
digitPlaces: 2,
|
||||
isloaded: false
|
||||
isloaded: false,
|
||||
popoverId: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -376,6 +379,9 @@ export default {
|
|||
})
|
||||
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
|
||||
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
|
||||
// document.addEventListener("click", () => {
|
||||
// this.popoverId = null
|
||||
// });
|
||||
},
|
||||
computed: {
|
||||
curSegmentGroup() {
|
||||
|
|
@ -411,6 +417,9 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickPopover(item) {
|
||||
this.popoverId = `popover-${item.segmentationId}_${item.segmentIndex}`
|
||||
},
|
||||
initThreshold() {
|
||||
if (!this.ThresholdTools.includes(this.activeTool)) {
|
||||
this.setToolActive(this.ThresholdTools[0])
|
||||
|
|
@ -559,7 +568,7 @@ export default {
|
|||
let item = arr[j]
|
||||
let bidirectional = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
|
||||
item.bidirectionalView = view
|
||||
if (!bidirectional) return false
|
||||
if (!bidirectional) continue
|
||||
annotation.visibility.setAnnotationVisibility(bidirectional.annotationUID, view)
|
||||
}
|
||||
this.resetViewport()
|
||||
|
|
@ -715,18 +724,24 @@ export default {
|
|||
|
||||
} else {
|
||||
let item = this.segmentList.find(i => i.segmentationId === this.segmentationId)
|
||||
let segmentIndex = item.segments[item.segments.length - 1].segmentIndex + 1
|
||||
let arr = item.segments.sort((a, b) => a.segmentIndex - b.segmentIndex)
|
||||
let segmentIndex = arr[item.segments.length - 1].segmentIndex + 1
|
||||
let obj = {
|
||||
segmentIndex: segmentIndex,
|
||||
segmentationId: this.segmentationId,
|
||||
SegmentLabel: `Segment ${segmentIndex}`,
|
||||
color: item.segments.length > this.colors.length ? this.colors[0] : this.colors[item.segments.length],
|
||||
color: item.segments.length >= this.colors.length ? this.colors[0] : this.colors[item.segments.length],
|
||||
stats: null,
|
||||
bidirectional: null,
|
||||
bidirectionalView: true,
|
||||
view: true,
|
||||
lock: false
|
||||
}
|
||||
if (item.segments.length >= this.colors.length) {
|
||||
let index = item.segments.length % this.colors.length
|
||||
let color = this.randomNearColor(this.colors[index], 4)
|
||||
obj.color = color
|
||||
}
|
||||
let id = await this.addOrUpdateSegment({ name: obj.SegmentLabel, color: obj.color, segmentIndex: obj.segmentIndex, segmentationId: obj.segmentationId })
|
||||
obj.id = id
|
||||
item.segments.push(obj)
|
||||
|
|
@ -1485,6 +1500,7 @@ export default {
|
|||
})
|
||||
}
|
||||
this.isloaded = false
|
||||
this.readingSegmentByConfig()
|
||||
}
|
||||
} catch (err) {
|
||||
this.loading = false
|
||||
|
|
@ -1589,6 +1605,87 @@ export default {
|
|||
b = parseInt(rgb[3], 16);
|
||||
return [r, g, b, alpha * 255];
|
||||
},
|
||||
randomNearColor(hex, range = 3) {
|
||||
if (!/^#([0-9a-fA-F]{6})$/.test(hex)) {
|
||||
throw new Error('请输入正确的 6 位十六进制颜色值,例如 #000000');
|
||||
}
|
||||
|
||||
const hexToRgb = (hex) => ({
|
||||
r: parseInt(hex.slice(1, 3), 16),
|
||||
g: parseInt(hex.slice(3, 5), 16),
|
||||
b: parseInt(hex.slice(5, 7), 16),
|
||||
});
|
||||
|
||||
const rgbToHsl = (r, g, b) => {
|
||||
r /= 255; g /= 255; b /= 255;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
let h = 0, s = 0;
|
||||
const l = (max + min) / 2;
|
||||
|
||||
if (max !== min) {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
|
||||
switch (max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
|
||||
h *= 60;
|
||||
}
|
||||
|
||||
return { h, s: s * 100, l: l * 100 };
|
||||
};
|
||||
|
||||
const hslToRgb = (h, s, l) => {
|
||||
h /= 360; s /= 100; l /= 100;
|
||||
let r, g, b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l;
|
||||
} else {
|
||||
const hue2rgb = (p, q, t) => {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return {
|
||||
r: Math.round(r * 255),
|
||||
g: Math.round(g * 255),
|
||||
b: Math.round(b * 255),
|
||||
};
|
||||
};
|
||||
|
||||
const rgbToHex = (r, g, b) =>
|
||||
`#${[r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')}`;
|
||||
|
||||
const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
||||
const randomOffset = (n) => (Math.random() * 2 - 1) * n;
|
||||
|
||||
const { r, g, b } = hexToRgb(hex);
|
||||
const { h, s, l } = rgbToHsl(r, g, b);
|
||||
|
||||
const nh = (h + randomOffset(range) + 360) % 360;
|
||||
const ns = clamp(s + randomOffset(range), 0, 100);
|
||||
const nl = clamp(l + randomOffset(range), 0, 100);
|
||||
|
||||
const rgb = hslToRgb(nh, ns, nl);
|
||||
return rgbToHex(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
@ -234,7 +234,7 @@ export default {
|
|||
// 如果行向量主要在 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;
|
||||
|
|
@ -281,6 +281,10 @@ export default {
|
|||
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()
|
||||
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)
|
||||
|
|
@ -488,8 +492,8 @@ export default {
|
|||
this.loading = true
|
||||
try {
|
||||
return await createImageIdsAndCacheMetaData({
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
modality: obj.Modality,
|
||||
imageIds: obj.ImageIds
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
|
|
|
|||
Loading…
Reference in New Issue