cornerstonejs 3D 網頁醫學影像套件
是很完整的3D醫學影像套件
他預設是 nodejs 套件
預設也是 nodejs 環境
然而 javascript 的通用性
讓 python django 也可以套用到網頁上
套用的重點是需要先安裝 nodejs
安裝完之後就可以使用 npm 了
然後在 django 裡面開一個資料夾
假設是 cornerstonejs
到 cornerstonejs 裡面用 npm 安裝 cornerstonejs
npm install @cornerstonejs/core npm install @cornerstonejs/tools npm install @cornerstonejs/dicom-image-loader npm install @cornerstonejs/nifti-volume-loader npm install --save-dev vite npm install @vitejs/plugin-react npm install @originjs/vite-plugin-commonjs npm install @cornerstonejs/calculate-suv npm install @cornerstonejs/polymorphic-segmentation npm install @cornerstonejs/demo-utils
然後程式參考
https://www.cornerstonejs.org/docs/tutorials/basic-volume
重點是在於 這一行
import { createImageIdsAndCacheMetaData } from '../../../../utils/demo/helpers';
這邊的 helper 必須要自定義
例如改成
import createImageIdsAndCacheMetaDataZ from './my-helper';
my-helper 要自己新增
裡面的程式參考
https://github.com/cornerstonejs/cornerstone3D/blob/main/utils/demo/helpers/createImageIdsAndCacheMetaData.js
也就是說
必須要自訂義自己的 createImageIdsAndCacheMetaData 才可以
可以參考我寫的
import { api } from 'dicomweb-client';
import dcmjs from 'dcmjs';
import { calculateSUVScalingFactors } from '@cornerstonejs/calculate-suv';
import { getPTImageIdInstanceMetadata } from './helpers/getPTImageIdInstanceMetadata';
import { utilities } from '@cornerstonejs/core';
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
import ptScalingMetaDataProvider from './helpers/ptScalingMetaDataProvider';
import { convertMultiframeImageIds } from './helpers/convertMultiframeImageIds';
import removeInvalidTags from './helpers/removeInvalidTags';
const { DicomMetaDictionary } = dcmjs.data;
const { calibratedPixelSpacingMetadataProvider, getPixelSpacingInformation } =
utilities;
/**
/**
* Uses dicomweb-client to fetch metadata of a study, cache it in cornerstone,
* and return a list of imageIds for the frames.
*
* Uses the app config to choose which study to fetch, and which
* dicom-web server to fetch it from.
*
* @returns {string[]} An array of imageIds for instances in the study.
*/
export default async function createImageIdsAndCacheMetaDataZ({
modality,
imagefs,
}) {
const SOP_INSTANCE_UID = '00080018';
const SERIES_INSTANCE_UID = '0020000E';
const MODALITY = '00080060';
const convertMultiframe = true;
// const modality = modality;
let imageIds = imagefs.map((ImageIdUrl) => {
const SeriesInstanceUID = '00000';
const SOPInstanceUIDToUse = '00001';
const prefix = 'wadors:';
var instanceMetaData = {
'00080018' : {Value : [ '00000' ]}, //SOP_INSTANCE_UID
'0020000E' : {Value : [ '00000' ]}, //SERIES_INSTANCE_UID
'00080060' : {Value : [ modality ]}, //MODALITY
};
const imageId = ImageIdUrl;
cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
imageId,
instanceMetaData
);
return imageId;
});
// if the image ids represent multiframe information, creates a new list with one image id per frame
// if not multiframe data available, just returns the same list given
if (convertMultiframe) {
imageIds = convertMultiframeImageIds(imageIds);
}
imageIds.forEach((imageId) => {
let instanceMetaData = cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId);
if (!instanceMetaData) {
return;
}
// It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower
instanceMetaData = removeInvalidTags(instanceMetaData);
if (instanceMetaData) {
// Add calibrated pixel spacing
const metadata = DicomMetaDictionary.naturalizeDataset(instanceMetaData);
const pixelSpacingInformation = getPixelSpacingInformation(metadata);
const pixelSpacing = pixelSpacingInformation?.PixelSpacing;
if (pixelSpacing) {
calibratedPixelSpacingMetadataProvider.add(imageId, {
rowPixelSpacing: parseFloat(pixelSpacing[0]),
columnPixelSpacing: parseFloat(pixelSpacing[1]),
type: pixelSpacingInformation.type,
});
}
}
});
return imageIds;
}
可以發現裡面很多資料是寫死的
因為在
var ct_imageIds = await createImageIdsAndCacheMetaDataZ({
modality : 'MD',
imagefs : ct_load_imagefs,
});
這邊 createImageIdsAndCacheMetaDataZ 原本是用來配合 dicomweb 來讀取影像的
類似於你需要另外架設一個 dicom server 來顯示dicom影像
但這對於我們只是單純展示 dicom web 的概念就衝突了
我們只會是一個 web server
所以這邊我們才需要自訂義
然後 ct_load_imagefs 這邊我們可以給例如
['wadouri:/dicomfun/showCornerstonejsDicom/22388', 'wadouri:/dicomfun/showCornerstonejsDicom/22389', 'wadouri:/dic…
這樣的網址
而這些網址也就是 /dicomfun/showCornerstonejsDicom/22388 則直接吐出 dicom 檔案即可
dicom 可以經由 django 產生
例如:
# 建立 DICOM 檔案資料集
ds = FileDataset(None, {}, file_meta=file_meta, preamble=b"\x00"*128)
# if data_params['Modality'] == 'PT':
# ds = pydicom.dcmread('C:/Users/T34808/Downloads/228-PET-43656083.dcm')
ds.PatientName = 'NONE'
ds.PatientID = '0'
ds.Modality = data_params['Modality']
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.SOPClassUID = file_meta.MediaStorageSOPClassUID
……
# 寫入 BytesIO
buffer = BytesIO()
ds.is_little_endian = True
ds.is_implicit_VR = False
ds.save_as(buffer)
buffer.seek(0)
# 回傳檔案
response = FileResponse(buffer, as_attachment=True, filename='generated_dicom.dcm')
response['Content-Type'] = 'application/dicom'
return response
所以只需要放進去設定好的dicom位置
再用 createAndCacheVolume 新增 Volume
var ct_imageIds = await createImageIdsAndCacheMetaDataZ({
modality : 'MD',
imagefs : ct_load_imagefs,
});
var ct_volume = await volumeLoader.createAndCacheVolume(ct_volumeId, {
imageIds: ct_imageIds,
});
ct_volume.load();
view_volumeIds = [{
volumeId: ct_volumeId,
}];
然後到 setVolumesForViewports 設定
setVolumesForViewports(
renderingEngine,
view_volumeIds,
[viewportId_axial, viewportId_sagittal, viewportId_coronal],
).then(() => {
// 設定初始
});
renderingEngine.renderViewports([viewportId_axial, viewportId_sagittal, viewportId_coronal]);
然後用vite 打包![]()
npx vite build
打包成 bundle.js
設定檔案 vite.config.js 可以參考以下
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { viteCommonjs } from '@originjs/vite-plugin-commonjs';
import { resolve } from 'path';
export default defineConfig({
plugins: [
react(),
// for dicom-parser
viteCommonjs(),
],
// seems like only required in dev mode
optimizeDeps: {
exclude: ['@cornerstonejs/dicom-image-loader'],
include: ['dicom-parser'],
},
worker: {
format: 'es',
},
base: '/static/cornerstonejs/', // Django 靜態資源對應的路徑
build: {
rollupOptions: {
input: {
app: resolve(__dirname, 'main.js'), // 這是你的入口檔案
},
output: {
entryFileNames: 'bundle.js',
},
},
outDir: '../static/cornerstonejs', // Django static 資料夾的相對路徑
assetsDir: '', // 避免產生 assets 子資料夾
emptyOutDir: true,
minify: false, // ❌ 不啟用 minify
sourcemap: true, // ✅ 建議同時開啟 sourcemap,方便瀏覽器中 debug
},
});
static/cornerstonejs 是對應 django 靜態資源位置
應該
基本上就能夠運作

希望對於有運用 cornerstonejs 的人有幫助喔
感恩![]()
題外話: cornerstonejs 相對冷門,但是你問 GPT如何套用他還是會給你答案,但是通常是 "幻覺",會給一堆不存在的函式,所以問GPT要有技巧,你問 npm , nodejs, javascript 都可以
但是問某個套件的應用方式,函式有哪些,這確實是緣木求魚,所以使用AI也是需要智慧的阿~![]()

留言板
歡迎留下建議與分享!希望一起交流!感恩!