<template>
    <div v-dialogDrag v-show="visible">
        <div class="el-dialog">
            <div class="el-dialog__header">
                <div class="btnBox">
                    <div :class="['tool-item', activeTool === 'histogram_RectangleROI' ? 'tool-item-active' : '']"
                        :title="$t('trials:histogram:button:histogramRectangleROI')"
                        @click.prevent="setToolActive('histogram_RectangleROI')">
                        <svg-icon icon-class="rectangle" class="svg-icon" />
                    </div>
                    <div :class="['tool-item', activeTool === 'histogram_CircleROI' ? 'tool-item-active' : '']"
                        :title="$t('trials:histogram:button:histogramCircleROI')"
                        @click.prevent="setToolActive('histogram_CircleROI')">
                        <svg-icon icon-class="oval" class="svg-icon" />
                    </div>
                    <div :class="['tool-item', activeTool === 'histogram_PlanarFreehandROI' ? 'tool-item-active' : '']"
                        :title="$t('trials:histogram:button:histogramPlanarFreehandROI')"
                        @click.prevent="setToolActive('histogram_PlanarFreehandROI')">
                        <svg-icon icon-class="polygon" class="svg-icon" />
                    </div>
                </div>
                <div class="title">{{ $t("trials:histogram:title:histogram") }}</div>
                <i class="el-icon-circle-close closeBtn" @click.stop="close"></i>
            </div>
            <div class="histogram">
                <div ref="chartContainer" style="width: 450px; height: 220px;" v-loading="loading"></div>
            </div>
        </div>
    </div>

</template>
<script>
import * as echarts from 'echarts/core';
import { LineChart } from 'echarts/charts';
import { AxisBreak } from "echarts/features.js"
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,
    AxisBreak,
    LabelLayout,
    UniversalTransition,
    CanvasRenderer
]);
import {
    getRenderingEngine,
    cache,
    utilities,
    Enums,
} from '@cornerstonejs/core';
import * as cornerstoneTools from '@cornerstonejs/tools';
const {
    ToolGroupManager,
    Enums: csToolsEnums,
    CrosshairsTool,
    annotation
} = cornerstoneTools;
const { MouseBindings, Events: toolsEvents } = csToolsEnums
export default {
    name: "histogram",
    props: {
        viewportKey: {
            type: String,
            default: 'viewport'
        },
        activeViewportIndex: {
            type: Number,
            default: 0
        },
        renderingEngineId: {
            type: String,
            required: true
        },
        visible: {
            type: Boolean,
            default: false
        },
        activeTool: {
            type: String,
            default: ''
        },
    },
    data() {
        return {
            chart: null,
            loading: false,
            defaultColors: [
                '#0ca8df',
                '#ffd10a',
                '#b6d634',
                '#3fbe95',
                '#785db0',
                '#5070dd',
                '#505372',
                '#ff994d',
                '#fb628b',
            ],
            colors: [],
            seriesData: {}
        }
    },
    mounted() {
        // this.initChart()
    },
    methods: {
        setToolActive(toolName) {
            const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
            const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
            if (this.activeTool === toolName) {
                if (toolName === CrosshairsTool.toolName) {
                    toolGroup.setToolDisabled(this.activeTool)
                } else {
                    toolGroup.setToolPassive(this.activeTool)
                }
                this.$emit('update:activeTool', '')
            } else {
                if (this.activeTool) {
                    if (toolName === CrosshairsTool.toolName) {
                        toolGroup.setToolDisabled(this.activeTool)
                    } else {
                        toolGroup.setToolPassive(this.activeTool)
                    }
                }
                toolGroup.setToolActive(toolName, {
                    bindings: [{ mouseButton: MouseBindings.Primary }]
                })
                this.$emit('update:activeTool', toolName)
            }
        },
        clearnAnnotation() {
            let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.toolName.includes('histogram_'));
            annotations.forEach(item => {
                annotation.state.removeAnnotation(item.annotationUID)
            })
            let viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3', 'viewport-MPR-0', 'viewport-MPR-1', 'viewport-MPR-2']
            for (let i = 0; i < viewportIds.length; i++) {
                const viewportId = viewportIds[i]
                let renderingEngine = getRenderingEngine(this.renderingEngineId)
                const viewport = renderingEngine.getViewport(viewportId)
                if (!viewport) continue
                viewport.render()
            }
        },
        close() {
            this.dispose()
            this.seriesData = {}
            this.clearnAnnotation()
            this.$emit("update:visible", false)
        },
        async init() {
            this.dispose()
            this.clearnAnnotation()
            this.colors = this.defaultColors
            let _data = await this.generateData();
            this.seriesData = {}
            this.seriesData['default'] = _data.seriesData
            this.seriesData['default'].color = this.colors[0]
            this.initChart()
        },
        async initToolValue(an) {
            let data = await this.generateData(an);
            if (this.seriesData[an.annotationUID]) {
                this.seriesData[an.annotationUID] = Object.assign(this.seriesData[an.annotationUID], data.seriesData)
            } else {
                let color = null;
                let keys = Object.keys(this.seriesData)
                if (keys.length >= this.defaultColors.length) {
                    let index = keys.length % this.defaultColors.length
                    color = this.randomNearColor(this.defaultColors[index], 30)
                    this.colors.push(color)
                } else {
                    color = this.defaultColors[keys.length]
                }
                annotation.config.style.setAnnotationStyles(an.annotationUID, {
                    color: this.hex2Rgb(color),
                    colorLocked: this.hex2Rgb(color),
                    colorSelected: this.hex2Rgb(color),
                });
                this.seriesData[an.annotationUID] = data.seriesData
                this.seriesData[an.annotationUID].color = color
            }
            this.initChart()
        },
        initChart() {
            if (!this.chart) {
                this.chart = echarts.init(this.$refs.chartContainer);
            }
            let seriesData = []
            Object.keys(this.seriesData).forEach(key => {
                seriesData.push(this.seriesData[key])
            })
            const option = {
                useUTC: true,
                title: {
                    show: false,
                    text: this.$t("trials:histogram:title:histogram"),
                    textStyle: {
                        color: "#fff"
                    },
                    left: 0,
                    top: 0
                },
                color: this.colors,
                grid: {
                    top: 0,
                    left: 50,
                    bottom: 80,
                    right: 20
                },
                tooltip: {
                    show: true,
                    trigger: 'axis'
                },
                xAxis: [
                    {
                        type: 'category',
                        axisLabel: {
                            textStyle: {
                                color: '#fff'
                            },

                        },
                        axisLine: { // 设置 x 轴线颜色
                            lineStyle: {
                                color: '#fff',
                            }
                        },
                    }
                ],
                yAxis: {
                    axisLabel: {
                        textStyle: {
                            color: '#fff',
                        }
                    },
                    axisLine: {
                        show: true,
                        lineStyle: {
                            color: '#fff',
                        }
                    },
                },
                dataZoom: [
                    {
                        type: 'inside',
                        xAxisIndex: 0,
                    },
                    {
                        type: 'slider',
                        xAxisIndex: 0,
                    }
                ],
                series: seriesData
            };
            this.chart.setOption(option);
        },
        async generateData(annotation) {
            var seriesData = [];
            var min = null;
            var max = null;
            let res = null
            if (annotation) {
                res = await this[`get${annotation.metadata.toolName.split('histogram_')[1]}Values`](this.renderingEngineId, `${this.viewportKey}-${this.activeViewportIndex}`, annotation)
            } else {
                res = await this.getCurrentSliceValuesFromVoxelManager(this.renderingEngineId, `${this.viewportKey}-${this.activeViewportIndex}`)
            }
            if (res) {
                let obj = {

                }
                res.values.forEach(item => {
                    if (obj[item]) {
                        obj[item]++
                    } else {
                        obj[item] = 1
                    }
                    if (min > item) {
                        min = item
                    }
                    if (max < item) {
                        max = item
                    }
                })
                Object.keys(obj).forEach(key => {
                    if (key > -1020) {
                        let arr = [key, obj[key]]
                        seriesData.push(arr)
                    }

                })
            }
            seriesData.sort((a, b) => a[0] - b[0])
            return {
                seriesData: {
                    type: 'line',
                    symbolSize: 0,
                    data: seriesData
                },
                min: min,
                max: max
            };
        },
        resize() {
            if (this.chart) {
                this.chart.resize()
            }
        },
        dispose() {
            if (this.chart) {
                this.chart.dispose()
                this.chart = null
            }
        },
        async getPlanarFreehandROIValues(
            renderingEngineId,
            viewportId,
            annotation,
        ) {
            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 metadata = volume.metadata || {};
            const modality = metadata.Modality || metadata.modality || 'UNKNOWN';
            const [dimX, dimY, dimZ] = volume.dimensions;

            const contourPointsWorld = this.getFreehandPointsFromAnnotation(annotation);
            if (!contourPointsWorld.length) {
                throw new Error('Freehand contour points not found');
            }

            // 世界坐标 -> IJK
            const contourPointsIJK = contourPointsWorld.map((p) =>
                utilities.transformWorldToIndex(volume.imageData, p)
            );

            const orientation = this.guessOrthogonalOrientation(
                viewport.getCamera().viewPlaneNormal
            );

            let values = [];

            if (orientation === Enums.OrientationAxis.AXIAL) {
                // 使用 x,y 做二维点，z 固定
                const k = this.clamp(
                    Math.round(this.avg(contourPointsIJK.map((p) => p[2]))),
                    0,
                    dimZ - 1
                );

                const polygon2D = contourPointsIJK.map((p) => [p[0], p[1]]);

                const minX = this.clamp(
                    Math.floor(Math.min(...polygon2D.map((p) => p[0]))),
                    0,
                    dimX - 1
                );
                const maxX = this.clamp(
                    Math.ceil(Math.max(...polygon2D.map((p) => p[0]))),
                    0,
                    dimX - 1
                );
                const minY = this.clamp(
                    Math.floor(Math.min(...polygon2D.map((p) => p[1]))),
                    0,
                    dimY - 1
                );
                const maxY = this.clamp(
                    Math.ceil(Math.max(...polygon2D.map((p) => p[1]))),
                    0,
                    dimY - 1
                );

                for (let y = minY; y <= maxY; y++) {
                    for (let x = minX; x <= maxX; x++) {
                        if (this.isPointInPolygon([x + 0.5, y + 0.5], polygon2D)) {
                            const raw = voxelManager.getAtIJK(x, y, k);
                            const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                            values.push(physicalValue);
                        }
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'AXIAL',
                    sliceIndex: k,
                    bounds: { minX, maxX, minY, maxY },
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            if (orientation === Enums.OrientationAxis.CORONAL) {
                // 使用 x,z 做二维点，y 固定
                const j = this.clamp(
                    Math.round(this.avg(contourPointsIJK.map((p) => p[1]))),
                    0,
                    dimY - 1
                );

                const polygon2D = contourPointsIJK.map((p) => [p[0], p[2]]);

                const minX = this.clamp(
                    Math.floor(Math.min(...polygon2D.map((p) => p[0]))),
                    0,
                    dimX - 1
                );
                const maxX = this.clamp(
                    Math.ceil(Math.max(...polygon2D.map((p) => p[0]))),
                    0,
                    dimX - 1
                );
                const minZ = this.clamp(
                    Math.floor(Math.min(...polygon2D.map((p) => p[1]))),
                    0,
                    dimZ - 1
                );
                const maxZ = this.clamp(
                    Math.ceil(Math.max(...polygon2D.map((p) => p[1]))),
                    0,
                    dimZ - 1
                );

                for (let z = minZ; z <= maxZ; z++) {
                    for (let x = minX; x <= maxX; x++) {
                        if (this.isPointInPolygon([x + 0.5, z + 0.5], polygon2D)) {
                            const raw = voxelManager.getAtIJK(x, j, z);
                            const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                            values.push(physicalValue);
                        }
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'CORONAL',
                    sliceIndex: j,
                    bounds: { minX, maxX, minZ, maxZ },
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            if (orientation === Enums.OrientationAxis.SAGITTAL) {
                // 使用 y,z 做二维点，x 固定
                const i = this.clamp(
                    Math.round(this.avg(contourPointsIJK.map((p) => p[0]))),
                    0,
                    dimX - 1
                );

                const polygon2D = contourPointsIJK.map((p) => [p[1], p[2]]);

                const minY = this.clamp(
                    Math.floor(Math.min(...polygon2D.map((p) => p[0]))),
                    0,
                    dimY - 1
                );
                const maxY = this.clamp(
                    Math.ceil(Math.max(...polygon2D.map((p) => p[0]))),
                    0,
                    dimY - 1
                );
                const minZ = this.clamp(
                    Math.floor(Math.min(...polygon2D.map((p) => p[1]))),
                    0,
                    dimZ - 1
                );
                const maxZ = this.clamp(
                    Math.ceil(Math.max(...polygon2D.map((p) => p[1]))),
                    0,
                    dimZ - 1
                );

                for (let z = minZ; z <= maxZ; z++) {
                    for (let y = minY; y <= maxY; y++) {
                        if (this.isPointInPolygon([y + 0.5, z + 0.5], polygon2D)) {
                            const raw = voxelManager.getAtIJK(i, y, z);
                            const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                            values.push(physicalValue);
                        }
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'SAGITTAL',
                    sliceIndex: i,
                    bounds: { minY, maxY, minZ, maxZ },
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            throw new Error('Only standard orthogonal orientation is supported');
        },
        async getCircleROIValues(
            renderingEngineId,
            viewportId,
            annotation,
        ) {
            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 metadata = volume.metadata || {};
            const modality = metadata.Modality || metadata.modality || 'UNKNOWN';
            const [dimX, dimY, dimZ] = volume.dimensions;

            const points = annotation?.data?.handles?.points;
            if (!points || points.length < 2) {
                throw new Error('CircleROI annotation points not found');
            }

            // 假设第一个点是圆心，第二个点在圆边
            const centerWorld = points[0];
            const edgeWorld = points[1];

            const centerIJK = utilities.transformWorldToIndex(volume.imageData, centerWorld);
            const edgeIJK = utilities.transformWorldToIndex(volume.imageData, edgeWorld);

            const orientation = this.guessOrthogonalOrientation(
                viewport.getCamera().viewPlaneNormal
            );

            let values = [];

            if (orientation === Enums.OrientationAxis.AXIAL) {
                // 圆位于 XY 平面，固定 z
                const cx = centerIJK[0];
                const cy = centerIJK[1];
                const k = this.clamp(Math.round(centerIJK[2]), 0, dimZ - 1);

                const dx = edgeIJK[0] - cx;
                const dy = edgeIJK[1] - cy;
                const radius = Math.sqrt(dx * dx + dy * dy);

                const minX = this.clamp(Math.floor(cx - radius), 0, dimX - 1);
                const maxX = this.clamp(Math.ceil(cx + radius), 0, dimX - 1);
                const minY = this.clamp(Math.floor(cy - radius), 0, dimY - 1);
                const maxY = this.clamp(Math.ceil(cy + radius), 0, dimY - 1);

                for (let y = minY; y <= maxY; y++) {
                    for (let x = minX; x <= maxX; x++) {
                        const dist2 = (x - cx) * (x - cx) + (y - cy) * (y - cy);
                        if (dist2 <= radius * radius) {
                            const raw = voxelManager.getAtIJK(x, y, k);
                            const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                            values.push(physicalValue);
                        }
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'AXIAL',
                    center: { x: cx, y: cy, z: k },
                    radius,
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            if (orientation === Enums.OrientationAxis.CORONAL) {
                // 圆位于 XZ 平面，固定 y
                const cx = centerIJK[0];
                const cz = centerIJK[2];
                const j = this.clamp(Math.round(centerIJK[1]), 0, dimY - 1);

                const dx = edgeIJK[0] - cx;
                const dz = edgeIJK[2] - cz;
                const radius = Math.sqrt(dx * dx + dz * dz);

                const minX = this.clamp(Math.floor(cx - radius), 0, dimX - 1);
                const maxX = this.clamp(Math.ceil(cx + radius), 0, dimX - 1);
                const minZ = this.clamp(Math.floor(cz - radius), 0, dimZ - 1);
                const maxZ = this.clamp(Math.ceil(cz + radius), 0, dimZ - 1);

                for (let z = minZ; z <= maxZ; z++) {
                    for (let x = minX; x <= maxX; x++) {
                        const dist2 = (x - cx) * (x - cx) + (z - cz) * (z - cz);
                        if (dist2 <= radius * radius) {
                            const raw = voxelManager.getAtIJK(x, j, z);
                            const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                            values.push(physicalValue);
                        }
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'CORONAL',
                    center: { x: cx, y: j, z: cz },
                    radius,
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            if (orientation === Enums.OrientationAxis.SAGITTAL) {
                // 圆位于 YZ 平面，固定 x
                const cy = centerIJK[1];
                const cz = centerIJK[2];
                const i = this.clamp(Math.round(centerIJK[0]), 0, dimX - 1);

                const dy = edgeIJK[1] - cy;
                const dz = edgeIJK[2] - cz;
                const radius = Math.sqrt(dy * dy + dz * dz);

                const minY = this.clamp(Math.floor(cy - radius), 0, dimY - 1);
                const maxY = this.clamp(Math.ceil(cy + radius), 0, dimY - 1);
                const minZ = this.clamp(Math.floor(cz - radius), 0, dimZ - 1);
                const maxZ = this.clamp(Math.ceil(cz + radius), 0, dimZ - 1);

                for (let z = minZ; z <= maxZ; z++) {
                    for (let y = minY; y <= maxY; y++) {
                        const dist2 = (y - cy) * (y - cy) + (z - cz) * (z - cz);
                        if (dist2 <= radius * radius) {
                            const raw = voxelManager.getAtIJK(i, y, z);
                            const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                            values.push(physicalValue);
                        }
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'SAGITTAL',
                    center: { x: i, y: cy, z: cz },
                    radius,
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            throw new Error('Only standard orthogonal orientation is supported');
        },
        async getRectangleROIValues(
            renderingEngineId,
            viewportId,
            annotation,
        ) {
            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 metadata = volume.metadata || {};
            const modality = metadata.Modality || metadata.modality || 'UNKNOWN';

            const [dimX, dimY, dimZ] = volume.dimensions;

            const points = annotation?.data?.handles?.points;
            if (!points || points.length < 4) {
                throw new Error('RectangleROI annotation points not found');
            }

            // 世界坐标 -> IJK
            const ijkPoints = points.map((p) =>
                utilities.transformWorldToIndex(volume.imageData, p)
            );

            // 根据当前切面方向，确定遍历哪两个轴
            const orientation = this.guessOrthogonalOrientation(
                viewport.getCamera().viewPlaneNormal
            );

            let values = [];

            if (orientation === Enums.OrientationAxis.AXIAL) {
                // 固定 z = k，遍历 x,y
                const k = this.clamp(Math.round(this.avg(ijkPoints.map((p) => p[2]))), 0, dimZ - 1);

                const minX = this.clamp(Math.floor(Math.min(...ijkPoints.map((p) => p[0]))), 0, dimX - 1);
                const maxX = this.clamp(Math.ceil(Math.max(...ijkPoints.map((p) => p[0]))), 0, dimX - 1);

                const minY = this.clamp(Math.floor(Math.min(...ijkPoints.map((p) => p[1]))), 0, dimY - 1);
                const maxY = this.clamp(Math.ceil(Math.max(...ijkPoints.map((p) => p[1]))), 0, dimY - 1);

                for (let y = minY; y <= maxY; y++) {
                    for (let x = minX; x <= maxX; x++) {
                        const raw = voxelManager.getAtIJK(x, y, k);
                        const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                        values.push(physicalValue);
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'AXIAL',
                    bounds: { minX, maxX, minY, maxY, k },
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            if (orientation === Enums.OrientationAxis.CORONAL) {
                // 固定 y = j，遍历 x,z
                const j = this.clamp(Math.round(this.avg(ijkPoints.map((p) => p[1]))), 0, dimY - 1);

                const minX = this.clamp(Math.floor(Math.min(...ijkPoints.map((p) => p[0]))), 0, dimX - 1);
                const maxX = this.clamp(Math.ceil(Math.max(...ijkPoints.map((p) => p[0]))), 0, dimX - 1);

                const minZ = this.clamp(Math.floor(Math.min(...ijkPoints.map((p) => p[2]))), 0, dimZ - 1);
                const maxZ = this.clamp(Math.ceil(Math.max(...ijkPoints.map((p) => p[2]))), 0, dimZ - 1);

                for (let z = minZ; z <= maxZ; z++) {
                    for (let x = minX; x <= maxX; x++) {
                        const raw = voxelManager.getAtIJK(x, j, z);
                        const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                        values.push(physicalValue);
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'CORONAL',
                    bounds: { minX, maxX, j, minZ, maxZ },
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            if (orientation === Enums.OrientationAxis.SAGITTAL) {
                // 固定 x = i，遍历 y,z
                const i = this.clamp(Math.round(this.avg(ijkPoints.map((p) => p[0]))), 0, dimX - 1);

                const minY = this.clamp(Math.floor(Math.min(...ijkPoints.map((p) => p[1]))), 0, dimY - 1);
                const maxY = this.clamp(Math.ceil(Math.max(...ijkPoints.map((p) => p[1]))), 0, dimY - 1);

                const minZ = this.clamp(Math.floor(Math.min(...ijkPoints.map((p) => p[2]))), 0, dimZ - 1);
                const maxZ = this.clamp(Math.ceil(Math.max(...ijkPoints.map((p) => p[2]))), 0, dimZ - 1);

                for (let z = minZ; z <= maxZ; z++) {
                    for (let y = minY; y <= maxY; y++) {
                        const raw = voxelManager.getAtIJK(i, y, z);
                        const physicalValue = this.convertToPhysicalValue(raw, metadata, modality);
                        values.push(physicalValue);
                    }
                }

                return {
                    modality,
                    valueType: this.getValueType(modality, metadata),
                    orientation: 'SAGITTAL',
                    bounds: { i, minY, maxY, minZ, maxZ },
                    count: values.length,
                    values,
                    stats: this.getStats(values),
                };
            }

            throw new Error('Only standard orthogonal orientation is supported');
        },
        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/ml，而不是真正SUV
            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));
        },
        avg(arr) {
            return arr.reduce((s, v) => s + v, 0) / arr.length;
        },
        getStats(values) {
            if (!values.length) {
                return { min: null, max: null, mean: null, stdDev: null };
            }

            let min = Infinity;
            let max = -Infinity;
            let sum = 0;

            for (const v of values) {
                if (v < min) min = v;
                if (v > max) max = v;
                sum += v;
            }

            const mean = sum / values.length;

            let varianceSum = 0;
            for (const v of values) {
                varianceSum += (v - mean) * (v - mean);
            }

            const stdDev = Math.sqrt(varianceSum / values.length);

            return { min, max, mean, stdDev };
        },
        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;
        },
        getFreehandPointsFromAnnotation(annotation) {
            if (!annotation?.data) {
                return [];
            }

            // 常见结构 1
            if (Array.isArray(annotation.data.contour?.polyline)) {
                return annotation.data.contour.polyline;
            }

            // 常见结构 2
            if (Array.isArray(annotation.data.polyline)) {
                return annotation.data.polyline;
            }

            // 常见结构 3
            if (Array.isArray(annotation.data.handles?.points)) {
                return annotation.data.handles.points;
            }

            return [];
        },
        isPointInPolygon(point, polygon) {
            const [px, py] = point;
            let inside = false;

            for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
                const [xi, yi] = polygon[i];
                const [xj, yj] = polygon[j];

                const intersect =
                    yi > py !== yj > py &&
                    px < ((xj - xi) * (py - yi)) / (yj - yi + 1e-12) + xi;

                if (intersect) {
                    inside = !inside;
                }
            }

            return inside;
        },
        hex2Rgb(hexValue, alpha = 1) {
            const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
            const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b);
            const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
            if (!rgb) {
                return hexValue;
            }
            const r = parseInt(rgb[1], 16),
                g = parseInt(rgb[2], 16),
                b = parseInt(rgb[3], 16);
            return `rgb(${r},${g},${b})`;
        },
        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);
        }
    }
}
</script>
<style lang="scss" scoped>
.el-dialog {
    position: fixed;
    top: 10px;
    margin-top: 110px;
    left: 220px;
    z-index: 9999;
    width: 450px;
    text-align: center;

    .el-dialog__header {
        text-align: left;
        padding: 5px;
        display: flex;
        align-items: center;
        justify-content: space-between;

        .btnBox {
            display: flex;
            align-items: center;
        }

        .closeBtn {
            cursor: pointer;
        }
    }

    .svg-icon {
        color: #fff;
        font-size: 20px;
    }

    .tool-item {
        padding: 5px;
        margin: 0 5px;
        border: 1px solid #333;
        // font-size: 20px;
        cursor: pointer;
    }

    .tool-item-active {
        background-color: #607d8b;
    }

    .tool-disabled {
        cursor: not-allowed;
    }

    .tool-frame {
        display: flex;
        flex-direction: row;
        justify-content: flex-start;
        align-items: center;
        // margin-right: 20px;
        padding: 5px;
        margin-bottom: 10px;
        border-bottom: 1px solid #404040;

        .icon {
            padding: 5px;
            border-right: 1px solid #404040;
            cursor: pointer;
            text-align: center;

            .svg-icon {
                font-size: 20px;
                color: #ddd;
            }
        }

        .select-wrapper {
            width: 60px;
            background-color: black;
            color: #ddd;
            border: none;
            font-size: 13px;
            outline: none;
        }

        .text {
            position: relative;
            font-size: 12px;
            margin-top: 5px;
            color: #d0d0d0;
            display: none;
        }
    }
}
</style>