基于cornerstone3D的dicom影像浏览器 第二十一章 显示DICOM TAGS

系列文章目录

第一章 下载源码 运行cornerstone3D example
第二章 修改示例crosshairs的图像源
第三章 vite+vue3+cornerstonejs项目创建
第四章 加载本地文件夹中的dicom文件并归档
第五章 dicom文件生成png,显示检查栏,序列栏
第六章 stack viewport 显示dicom序列
第七章 在Displayer四个角落显示文字
第八章 在Displayer中显示图像方位
第九章 自动加载、清空显示、修改布局
第十章 显示标尺
第十一章 测量工具
第十二章 镜像、负像、旋转、伪彩、复位
第十三章 自定义垂直滚动条
第十四章 参考线、同步调窗、同步缩放、同步移动
第十五章 预设窗值
第十六章 工具栏svg按钮
第十七章 同步滚动
第十八章 自定义序列自动播放条
第十九章 显示overlay
第二十章 显示多帧图


文章目录


前言

读取Dicom文件中所有tags并显示到对话框中,提供查找功能。

效果如下:


一、读取标签

在image.js中增加读取标签列表的两个函数,readTagList读取普通tag, readTagSQ读取vr==SQ的序列标签

javascript 复制代码
async readTagList() {
		let tagdata = [];
		const image = await imageLoader.loadAndCacheImage(this.imageId);
		let charset = image.data.string("x00080005") || "ISO_IR 100";
		let isUtf8 = charset.indexOf("ISO_IR 192") != -1;
		const tags2 = [];
		const tags = [];
		for (let key in image.data.elements) {
			const atag = {};
			const elem = image.data.elements[key];
			atag.tagid =
				"(" + key.substring(1, 5) + "," + key.substring(5) + ")";
			atag.tagid = atag.tagid.toUpperCase();
			atag.vr = elem.vr;
			atag.len = elem.length;
			atag.vm = image.data.numStringValues(key) || 0;
			atag.desc = DcmTagsDesc[key] || "UnkownTag";

			const isMeta = key.substring(1, 5) == "0002";

			if (key === "x7fe00010") {
				const pixLen = elem.length > 32 ? 32 : elem.length;
				atag.value = this.readNPixelAsHexString(
					image.data.byteArray,
					elem,
					pixLen
				);
			} else {
				if (atag.vr === "US" || atag.vr === "UL") {
					atag.value = image.data.uint16(key) + "";
				} else if (atag.vr === "FL") {
					atag.value = image.data.float(key) + "";
				} else if (atag.vr === "FD") {
					atag.value = image.data.double(key) + "";
				} else if (atag.vr === "SS") {
					atag.value = image.data.int16(key) + "";
				} else if (atag.vr === "SQ") {
					atag.value = "";
					if (isMeta) {
						tags2.push(atag);
					} else {
						tags.push(atag);
					}
					this.readTagSQ(
						isMeta ? tags2 : tags,
						elem.items,
						isUtf8,
						1
					);

					const endsq = {};
					endsq.tagid = "(FFFE,E0DD)";
					endsq.vr = "";
					endsq.vm = 0;
					endsq.len = 0;
					endsq.desc = DcmTagsDesc["xfffee0dd"];
					endsq.value = "";
					if (isMeta) {
						tags2.push(endsq);
					} else {
						tags.push(endsq);
					}
				} else {
					atag.value = readTagAsString(
						image.data.byteArray,
						elem.dataOffset,
						elem.length
					);
					if (atag.value) {
						atag.value = decodeChinese(atag.value, isUtf8);
					}
				}
			}

			//dicom tag第一个值不为02的放列表后面显示
			if (atag.vr !== "SQ") {
				if (isMeta) {
					tags2.push(atag);
				} else {
					tags.push(atag);
				}
			}
		}

		tagdata = [...tags2, ...tags];

		return tagdata;
	}

	readTagSQ(arr, items, isUtf8, level) {
		items.forEach(item => {
			const image = item.dataSet;
			const itemtag = {};
			itemtag.tagid =
				"(" +
				item.tag.substring(1, 5) +
				"," +
				item.tag.substring(5) +
				")";
			itemtag.tagid = getNSpace(level) + itemtag.tagid;
			itemtag.tagid = itemtag.tagid.toUpperCase();
			itemtag.len = item.length;
			itemtag.vr = "";
			itemtag.value = "";
			itemtag.vm = 0;
			itemtag.desc = DcmTagsDesc[item.tag] || "";
			arr.push(itemtag);

			for (let key in image.elements) {
				const elem = image.elements[key];
				const atag = {};
				atag.tagid =
					"(" +
					elem.tag.substring(1, 5) +
					"," +
					elem.tag.substring(5) +
					")";
				if (elem.tag === "xfffee00d")
					atag.tagid = getNSpace(level) + atag.tagid;
				else atag.tagid = getNSpace(level + 1) + atag.tagid;
				atag.tagid = atag.tagid.toUpperCase();
				atag.vr = elem.vr;
				atag.len = elem.length;
				atag.vm = image.numStringValues(elem.tag) | 0;
				atag.desc = DcmTagsDesc[elem.tag] || "UnkownTag";

				if (elem.tag === "x7fe00010") {
					const pixLen = elem.length > 32 ? 32 : elem.length;
					atag.value = this.readNPixelAsHexString(
						image.byteArray,
						elem,
						pixLen
					);
				} else {
					if (atag.vr === "US" || atag.vr === "UL") {
						atag.value = image.uint16(elem.tag) + "";
					} else if (atag.vr === "FL") {
						atag.value = image.float(elem.tag) + "";
					} else if (atag.vr === "FD") {
						atag.value = image.double(elem.tag) + "";
					} else if (atag.vr === "SS") {
						atag.value = image.int16(elem.tag) + "";
					} else if (atag.vr === "SQ") {
						atag.value = "";
						arr.push(atag);
						this.readTagSQ(arr, elem.items, isUtf8, level + 2);
						const endsq = {};
						endsq.tagid = "(FFFE,E0DD)";
						endsq.vr = "";
						endsq.vm = 0;
						endsq.len = 0;
						endsq.desc = DcmTagsDesc["xfffee0dd"];
						endsq.value = "";
						arr.push(endsq);
					} else {
						atag.value = readTagAsString(
							image.byteArray,
							elem.dataOffset,
							elem.length
						);
						
						if (atag.value) {
							atag.value = decodeChinese(atag.value, isUtf8);
						}
					}

					if (atag.vr !== "SQ") arr.push(atag);
				}
			}
		});
	}

二、显示标签列表

1. 新建组件DcmTagsWnd.vue

fullData 与 dcmtagData关联tag list数据

javascript 复制代码
<script setup name="DcmTagsWnd">
import { ref, watch } from "vue";
import { debounce } from "@kitware/vtk.js/macros";

const dialogVisible = ref(false);
const fullData = ref(null);
const dcmtagData = ref(null);
const findText = ref("");

watch(findText, val => {
	if (val.length > 0) {
		const debouncedFilter = debounce(() => {
			const lval = val.toLowerCase();
			const newData = fullData.value.filter(item => {
				return (
					item.tagid.toLowerCase().includes(lval) ||
					item.desc.toLowerCase().includes(lval) ||
					item.value.toLowerCase().includes(lval)
				);
			});

			dcmtagData.value = newData;
		}, 300);

		debouncedFilter();
	}
});

const show = data => {
	fullData.value = data || [];
	dcmtagData.value = data || [];
	dialogVisible.value = true;
};

defineExpose({ show });
</script>

<template>
	<div class="dcmtag">
		<el-dialog
			title="DICOM TAGS"
			v-model="dialogVisible"
			width="820px"
			draggable
		>
			<el-table :data="dcmtagData" border height="600px">
				<el-table-column
					property="tagid"
					label="TagId"
					width="100"
					:show-overflow-tooltip="true"
				></el-table-column>
				<el-table-column
					property="vr"
					label="VR"
					width="50"
				></el-table-column>
				<el-table-column
					property="vm"
					label="VM"
					width="50"
				></el-table-column>
				<el-table-column
					property="len"
					label="Length"
					width="80"
				></el-table-column>
				<el-table-column
					property="desc"
					label="Description"
					width="250"
					:show-overflow-tooltip="true"
				></el-table-column>
				<el-table-column
					property="value"
					label="Value"
					width="290"
					:show-overflow-tooltip="true"
				></el-table-column>
			</el-table>
			<el-input
				v-model="findText"
				placeholder="Find Text..."
				clearable
				style="width: 300px; margin: 2px 6px"
			></el-input>
		</el-dialog>
	</div>
</template>

<style lang="scss" scoped>
.dcmtag {
	user-select: text;
}

.finder {
	display: flex;
	.finder__label {
		width: 60px;
	}
}

:deep(.el-dialog__header) {
	background: #eee;
	height: 40px;
	padding: 8px 8px;
	border: none;
	text-align: left;
}

:deep(.el-dialog__title) {
	color: #444;
}

:deep(.el-dialog) {
	padding: 0;
}

:deep(.el-table__header .is-leaf) {
	background-color: white;
	color: #444;
}

:deep(.el-table__cell) {
	background-color: white;
	color: #444;
	padding: 4px 0px;
}

:deep(.el-table--enable-row-hover .el-table__body tr:hover > td) {
	background-color: lightblue;
}

:deep(.el-table--border .el-table__cell:first-child .cell) {
	padding-left: 4px;
}
</style>

2. 调用流程

  • 工具栏上添加"DICOM标签"按钮,参考前面章节
  • DisplayerArea中添加DcmTagsWnd组件
  • 选中窗口,点击按钮
  • 读取tag list, 传入DcmTagsWnd并显示对话框

view2d.vue

javascript 复制代码
async function OnToolbarAction(action) {
	switch (action.name) {
		case "openFolder":
			{
				const fileHandles = await openFolder();
				if (Array.isArray(fileHandles)) {
					fileHandles.forEach(fileHandle => {
						fileHandle.getFile().then(file => {
							archiveStore.archiveFile(file);
						});
					});
				} else {
					console.error("openFolder, fileHandles is not an array");
				}
			}

			break;
		...
		case "setWindow":
			displayArea.value.setViewportWindow(action.wl, action.ww);
			break;
		case "dcmtag":
			displayArea.value.showDcmTagsDialog();
			break;
	}
}

DisplayerArea.vue

javascript 复制代码
import DcmTagsWnd from "./DcmTagsWnd.vue";
const dcmTagsWnd = ref(null);
const showDcmTagsDialog = async () => {
    // console.log("showDcmTagsWnd");
    if (selected.length>0) {
        const disp = dispRefs[selected[0]];
        const tagList = await disp.getTagList();
        // console.log("showDcmTagsWnd tagList:", tagList);
        dcmTagsWnd.value.show(tagList);
    }
}

<template>
	<div class="displayarea">
		<DcmTagsWnd ref="dcmTagsWnd" />
		...
</template>

总结

  1. 遍历dicom文件tag,获取tag list
  2. 对话框中显示tag list,并提供查找功能
相关推荐
量化分析码农18 分钟前
股票历史数据的复权参数对照:多级别返回的字段与数值差异
java·javascript·后端
亿元程序员2 小时前
太牛了!这AI做出来的游戏,居然还能变现!
前端
hellice2 小时前
闪传 FlashDrop 实战:我用 300 行 Node 代码做了个「4 位取件码」临时文件中转站
前端·产品
用户0829773260052 小时前
RTX 5500 MIG 踩坑记录:切分之后模型跑不起来,排错整整一下午
前端
Lank_M2 小时前
AI 放大后的图更清楚,但它不能当原图入库
前端
Swot2 小时前
SvelteKit 数据流研究:`+page.js` 和 `+page.server.js` 到底谁在跑,数据又去了哪儿
前端·后端
用户4147679304152 小时前
Windows 触摸屏上 Swiper 轮播滑不动?可能是内部滚动容器抢走了你的手势
前端
张明君644512 小时前
从一个 Referer 引发的 403,到 SSR 服务失稳:Astro 流式渲染与缓存失效分析
前端
律宏阔2 小时前
微信小程序 + Taro + TDesign 去掉 [&&&],让普通 Tailwind 类接管局部样式
前端·微信小程序·taro
费曼学习法2 小时前
把 3 个内部服务封装成 MCP Server 的工程复盘:进程模型、超时与错误契约
前端