wangxiaoshuang 2026-04-03 14:02:05 +08:00
parent ee6c5bf8a5
commit 366ec62cd7
2 changed files with 367 additions and 0 deletions

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="1775115523115" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5347" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M458.105263 970.105263h-215.578947V264.084211h215.578947V970.105263z m-161.68421-53.894737h107.789473V317.978947h-107.789473V916.210526z" fill="#ffffff" p-id="5348"></path><path d="M296.421053 970.105263h-215.578948V479.663158h215.578948V970.105263z m-161.684211-53.894737h107.789474V533.557895h-107.789474V916.210526zM943.157895 970.105263h-215.578948V479.663158h215.578948V970.105263z m-161.684211-53.894737h107.789474V533.557895h-107.789474V916.210526zM619.789474 970.105263h-215.578948V53.894737h215.578948v916.210526z m-161.684211-53.894737h107.789474V107.789474h-107.789474v808.421052z" fill="#ffffff" p-id="5349"></path><path d="M781.473684 970.105263h-215.578947V264.084211h215.578947V970.105263z m-161.68421-53.894737h107.789473V317.978947h-107.789473V916.210526z" fill="#ffffff" p-id="5350"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,366 @@
<template>
<div class="histogram">
<div ref="chartContainer" style="width: 490px; height: 290px;" v-loading="loading"></div>
</div>
</template>
<script>
import * as echarts from 'echarts/core';
import { LineChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
DataZoomComponent,
LegendComponent,
DatasetComponent,
// (filter, sort)
TransformComponent
} from 'echarts/components';
//
import { LabelLayout, UniversalTransition } from 'echarts/features';
// Canvas CanvasRenderer SVGRenderer
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent,
LegendComponent,
LineChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
import {
getRenderingEngine,
cache,
utilities,
Enums,
} from '@cornerstonejs/core';
export default {
name: "histogram",
props: {
viewportKey: {
type: String,
default: 'viewport'
},
activeViewportIndex: {
type: Number,
default: 0
},
renderingEngineId: {
type: String,
required: true
},
},
data() {
return {
chart: null
}
},
methods: {
init() {
},
initChart() {
this.chart = echarts.init(this.$refs.chartContainer);
var formatTime = echarts.time.format;
var _data = this.generateData1();
const option = {
// Choose axis ticks based on UTC time.
useUTC: true,
title: {
text: 'Intraday Chart with Breaks (Single Day)',
left: 'center'
},
tooltip: {
show: true,
trigger: 'axis'
},
xAxis: [
{
type: 'time',
interval: 1000 * 60 * 30,
axisLabel: {
showMinLabel: true,
showMaxLabel: true,
formatter: (value, index, extra) => {
if (!extra || !extra.break) {
// The third parameter is `useUTC: true`.
return formatTime(value, '{HH}:{mm}', true);
}
// Only render the label on break start, but not on break end.
if (extra.break.type === 'start') {
return (
formatTime(extra.break.start, '{HH}:{mm}', true) +
'/' +
formatTime(extra.break.end, '{HH}:{mm}', true)
);
}
return '';
}
},
breakLabelLayout: {
// Disable auto move of break labels if overlapping,
// and use `axisLabel.formatter` to control the label display.
moveOverlap: false
},
breaks: [
{
start: _data.breakStart,
end: _data.breakEnd,
gap: 0
}
],
breakArea: {
expandOnClick: false,
zigzagAmplitude: 0,
zigzagZ: 200
}
}
],
yAxis: {
type: 'value',
min: 'dataMin'
},
dataZoom: [
{
type: 'inside',
xAxisIndex: 0
},
{
type: 'slider',
xAxisIndex: 0
}
],
series: [
{
type: 'line',
symbolSize: 0,
data: _data.seriesData
}
]
};
this.chart.setOption(option);
},
generateData1() {
var seriesData = [];
var time = new Date('2024-04-09T09:30:00Z');
var endTime = new Date('2024-04-09T15:00:00Z').getTime();
var breakStart = new Date('2024-04-09T11:30:00Z').getTime();
var breakEnd = new Date('2024-04-09T13:00:00Z').getTime();
for (var val = 1669; time.getTime() <= endTime;) {
if (time.getTime() <= breakStart || time.getTime() >= breakEnd) {
val =
val +
Math.floor((Math.random() - 0.5 * Math.sin(val / 1000)) * 20 * 100) /
100;
val = +val.toFixed(2);
seriesData.push([time.getTime(), val]);
}
time.setMinutes(time.getMinutes() + 1);
}
return {
seriesData: seriesData,
breakStart: breakStart,
breakEnd: breakEnd
};
},
resize() {
if (this.chart) {
this.chart.resize()
}
},
dispose() {
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
async getCurrentSliceValuesFromVoxelManager(renderingEngineId, viewportId) {
const renderingEngine = getRenderingEngine(renderingEngineId);
const viewport = renderingEngine.getViewport(viewportId);
const volumeId = viewport.getVolumeId();
if (!volumeId) {
throw new Error('No volumeId found on viewport');
}
const volume = cache.getVolume(volumeId);
if (!volume) {
throw new Error(`Volume not found in cache: ${volumeId}`);
}
if (!volume.load) {
await volume.load();
}
const voxelManager = volume.voxelManager;
if (!voxelManager) {
throw new Error('voxelManager not found on volume');
}
const [dimX, dimY, dimZ] = volume.dimensions;
const camera = viewport.getCamera();
const focalPoint = camera.focalPoint;
const orientation = this.guessOrthogonalOrientation(camera.viewPlaneNormal);
const ijk = utilities.transformWorldToIndex(volume.imageData, focalPoint);
const i = this.clamp(Math.round(ijk[0]), 0, dimX - 1);
const j = this.clamp(Math.round(ijk[1]), 0, dimY - 1);
const k = this.clamp(Math.round(ijk[2]), 0, dimZ - 1);
const modality =
volume.metadata?.Modality ||
volume.metadata?.modality ||
'UNKNOWN';
let width, height, values;
if (orientation === Enums.OrientationAxis.AXIAL) {
width = dimX;
height = dimY;
values = new Float32Array(width * height);
let idx = 0;
for (let y = 0; y < dimY; y++) {
for (let x = 0; x < dimX; x++) {
const raw = voxelManager.getAtIJK(x, y, k);
values[idx++] = this.convertToPhysicalValue(raw, volume.metadata, modality);
}
}
return { orientation: 'AXIAL', sliceIndex: k, width, height, values, modality };
}
if (orientation === Enums.OrientationAxis.CORONAL) {
width = dimX;
height = dimZ;
values = new Float32Array(width * height);
let idx = 0;
for (let z = 0; z < dimZ; z++) {
for (let x = 0; x < dimX; x++) {
const raw = voxelManager.getAtIJK(x, j, z);
values[idx++] = this.convertToPhysicalValue(raw, volume.metadata, modality);
}
}
return { orientation: 'CORONAL', sliceIndex: j, width, height, values, modality };
}
if (orientation === Enums.OrientationAxis.SAGITTAL) {
width = dimY;
height = dimZ;
values = new Float32Array(width * height);
let idx = 0;
for (let z = 0; z < dimZ; z++) {
for (let y = 0; y < dimY; y++) {
const raw = voxelManager.getAtIJK(i, y, z);
values[idx++] = this.convertToPhysicalValue(raw, volume.metadata, modality);
}
}
return { orientation: 'SAGITTAL', sliceIndex: i, width, height, values, modality };
}
throw new Error('Unsupported orientation');
},
convertToPhysicalValue(storedValue, metadata, modality) {
if (modality === 'CT') {
return this.convertCTToHU(storedValue, metadata);
}
if (modality === 'PT' || modality === 'PET') {
return this.convertPETToSUV(storedValue, metadata);
}
return storedValue;
},
convertCTToHU(value, metadata) {
const slope = Number(metadata?.RescaleSlope ?? metadata?.rescaleSlope ?? 1);
const intercept = Number(
metadata?.RescaleIntercept ?? metadata?.rescaleIntercept ?? 0
);
return value * slope + intercept;
},
convertPETToSUV(value, metadata) {
// SUV
const units = metadata?.Units || metadata?.units || '';
const correctedImage = metadata?.CorrectedImage || metadata?.correctedImage;
// SUV
if (
typeof units === 'string' &&
units.toUpperCase().includes('SUV')
) {
return value;
}
// Rescale
const slope = Number(metadata?.RescaleSlope ?? metadata?.rescaleSlope ?? 1);
const intercept = Number(
metadata?.RescaleIntercept ?? metadata?.rescaleIntercept ?? 0
);
const activityConcentration = value * slope + intercept;
// metadata suvFactor使
const suvFactor = metadata?.suvFactor ?? metadata?.SUVFactor;
if (suvFactor != null) {
return activityConcentration * Number(suvFactor);
}
// SUV退 rescale
// Bq/mlSUV
return activityConcentration;
},
getValueType(modality, metadata) {
if (modality === 'CT') {
return 'HU';
}
if (modality === 'PT' || modality === 'PET') {
const units = metadata?.Units || metadata?.units || '';
if (typeof units === 'string' && units.toUpperCase().includes('SUV')) {
return 'SUV';
}
if (metadata?.suvFactor != null || metadata?.SUVFactor != null) {
return 'SUV';
}
return 'PET';
}
return 'RAW';
},
clamp(v, min, max) {
return Math.max(min, Math.min(max, v));
},
guessOrthogonalOrientation(viewPlaneNormal) {
const [nx, ny, nz] = viewPlaneNormal.map(Math.abs);
if (nz >= nx && nz >= ny) {
return Enums.OrientationAxis.AXIAL;
}
if (ny >= nx && ny >= nz) {
return Enums.OrientationAxis.CORONAL;
}
return Enums.OrientationAxis.SAGITTAL;
}
}
}
</script>
<style lang="scss" scoped></style>